What are Database Triggers (BeforeSave and AfterSave)?

Last updated: July 2026

A database trigger is a code hook that runs automatically on data events — before a save to validate, after it to react. The idea has two lineages sharing one principle: the classical SQL trigger, procedural code living inside the database engine, and the modern application-level hookbeforeSave, afterSave, beforeDelete — JavaScript registered per class in the backend. Both encode the same promise: the rule fires for every write, no matter which client, SDK, or script performed it — which is precisely what client-side validation can never promise.

Key takeaways

QuestionAnswer
The principleCode bound to data events — automatic, per-table/class, every write path
Before =Validate · normalize · default — can still change or abort the write
After =React — count, notify, sync; the write already happened, so be idempotent
The two formsSQL triggers inside the engine · JS hooks in the backend process
The classic bugsHidden logic · infinite self-firing loops · slow triggers blocking every save

The hook model in action

A validation rule and its enforcement, from both ends — the server defines it once, and every client everywhere inherits it:

// JavaScript — Cloud Code (cloud/main.js)
// beforeSave: validate + normalize — can still change or ABORT the write
Parse.Cloud.beforeSave('Review', (req) => {
  const stars = req.object.get('stars');
  if (stars < 1 || stars > 5) throw 'Stars must be between 1 and 5';
  const comment = req.object.get('comment');
  if (comment && comment.length > 500) {
    req.object.set('comment', comment.slice(0, 497) + '…');
  }
});

// afterSave: side effects — the write already happened; be idempotent
Parse.Cloud.afterSave('Review', async (req) => {
  if (req.object.existed()) return; // count only NEW reviews, once
  await updateAverageStars(req.object.get('movie'));
});

The full hook family extends the same shape across the data lifecycle: beforeSave/afterSave, beforeDelete (block deleting an Album that still has Photos) and afterDelete (clean up its children), plus beforeFind/afterFind to rewrite queries and strip fields from results — CRUD, wrapped.

The classical form: SQL triggers

CREATE TRIGGER audit_price_change
AFTER UPDATE ON products
FOR EACH ROW                              -- row-level: fires per affected row
WHEN (OLD.price IS DISTINCT FROM NEW.price)
EXECUTE FUNCTION log_price_change();      -- OLD and NEW hold both versions

The taxonomy every database shares, per the PostgreSQL and MySQL references: timing — BEFORE (may modify NEW or abort), AFTER (reacts to the committed row), INSTEAD OF (replaces the operation, mainly for views); event — insert, update, delete; granularity — row-level versus statement-level (a 10-row update fires a row trigger 10 times, a statement trigger once). Two semantics worth engraving: SQL triggers run inside the same transaction as the write — a trigger failure rolls the whole operation back — and AFTER triggers only fire for writes that actually succeeded.

Before vs. after: chosen by job

Before hooksAfter hooks
Can change the dataYes — mutate, default, truncateNo — it’s written
Can abort the writeYes — throw, and the save failsNo
Right forValidation, normalization, computed fieldsCounters, notifications, sync, audit
Wrong forSide effects — if the save fails later, the effect already firedAnything that should have blocked the write
Failure behaviorRejects the operation, error to the clientOften fire-and-forget — errors land in logs
DisciplineFast — it blocks every saveIdempotent — it may run again

The corollaries no explainer states: a side effect in a before-hook is a bug by construction (the email sends, then the save fails), and an after-hook that isn’t idempotent is a duplicate counter waiting for a retry. In hook systems like Back4app’s, afterSave completes after the client already got its response — reactions are asynchronous by design, so their failures must be tolerable and logged.

Write lifecycle through before and after hooksA write from any client first passes the before hook, which can validate, modify, or abort it. If allowed, the database commits the write, and the after hook then reacts with side effects such as counters, notifications, and webhooks, which must be idempotent because they may run more than once.

throw

allow (possibly modified)

Any client
SDK · REST · script

beforeSave
validate · normalize

Save rejected —
error to the client

Write commits

afterSave
side effects, idempotent

Counters · notifications ·
outgoing webhooks

A write from any client first passes the before hook, which can validate, modify, or abort it. If allowed, the database commits the write, and the after hook then reacts with side effects such as counters, notifications, and webhooks, which must be idempotent because they may run more than once.

Hooks vs. SQL triggers

Application hooks (beforeSave/afterSave)SQL triggers
LanguageJavaScript + the full SDK and ecosystemSQL / procedural SQL dialects
External callsYes — APIs, push, webhooksEssentially no — and shouldn’t
Lives inYour codebase: versioned, testable, deployedThe schema: inside the database
TransactionBefore-hooks gate the write; after-hooks run post-responseSame transaction — failure rolls back
BindsEvery request through the backend APIEvery write to the table, from anything
Blind spotDirect-to-database writes bypass itLogic invisible to application debuggers

The last row is the honest symmetry: each form’s guarantee is scoped to its layer. A SQL trigger catches even a rogue psql session but hides logic from application tooling; an API-level hook binds every client path through the backend but not raw database access — which is why platforms that own the API gateway (all traffic flows through it) get the practical best of both. The ORM footnote belongs here too: ORM hooks fire only through the ORM — bulk operations and raw SQL walk right past them.

The pitfalls, honestly

Hidden logic is the classic: a developer debugs their own code for hours while a trigger silently rewrites values — triggers are invisible at the call site, so document them and keep them few. Infinite loops: a trigger writing to its own table (or an afterSave saving its own object) re-fires itself; guard on what changed, and give recursion a terminating condition before it finds one for you. Synchronous cost: before-hooks sit inside every write’s latency — a 200 ms hook makes every save 200 ms slower, and bulk writes multiply row-level firings by the row count. Cascade chains: triggers that fire triggers that fire triggers turn one insert into an archaeology project. The umbrella rule from decades of practice: triggers enforce rules; the moment one starts orchestrating a workflow, move the workflow to functions or jobs and let the trigger just enqueue.

Common use cases

  • Validation every client obeys — the stars-between-1-and-5 rule, enforced against forged requests and future SDKs alike.
  • Audit trails — who changed what, when, written by the event itself rather than by cooperative clients.
  • Denormalized counters and computed fields — averages, counts, and search-friendly duplicates kept true at write time.
  • Cascading integrity — refuse deletes with children, or clean the children up after.
  • Reactive side effects — an afterSave firing a push notification or an outgoing webhook: the database event becoming an integration event.

Which hook where? A decision matrix

The jobThe hook
Reject bad databeforeSave — throw
Trim, default, canonicalizebeforeSave — mutate
Update a counter or aggregateafterSave — idempotently
Notify a person or systemafterSave → push / webhook / queue
Block dangerous deletesbeforeDelete
Clean up after deletesafterDelete
Enforce per-user query scopebeforeFind
Long or slow reactionafterSave enqueues a job — never does the work inline

Limitations and trade-offs

  • Invisibility is the price of automaticity. Logic nobody calls is logic nobody remembers; naming, docs, and code review keep the magic auditable.
  • Hooks scope to their layer. API hooks miss direct DB writes; SQL triggers miss nothing but hide from your tooling — know which blind spot you chose.
  • Write latency is the budget. Every before-hook spends it; measure hooks like you measure queries.
  • After-hooks are eventually consistent. Counters lag by milliseconds and can double-fire — design reads (and retries) accordingly.
  • Triggers don’t replace constraints. Unique indexes, foreign keys, and ACLs enforce cheaper and earlier; triggers pick up where declarative rules stop.

Triggers on Back4app

Back4app is an open-source Backend-as-a-Service (BaaS) platform that combines a managed database, auto-generated REST and GraphQL APIs, authentication, file storage, and Cloud Code serverless functions. Triggers here are the data-event family of Cloud Code: register Parse.Cloud.beforeSave('Review', …) once — the code tabs show the whole pattern — and the rule binds every write arriving through REST, GraphQL, any SDK, or the dashboard, because the API gateway is the one path to the data. Hooks receive rich context (request.object, request.original, request.user, master-key status), so validation can differ for admins, beforeFind can scope queries per user on top of ACLs, and after-hooks have the full JavaScript ecosystem for the reactions SQL triggers can’t reach — push notifications, outgoing webhooks, job enqueues. Deployed with your code, versioned in git, testable like any function: the trigger’s guarantee, without the trigger’s archaeology.

Frequently asked questions

What is a database trigger?

Procedural code that executes automatically when a data event — insert, update, delete — occurs on a specific table or class. The classical form lives inside the SQL database; the modern application-level form is a hook function like beforeSave or afterSave that the backend runs around every write.

What are the types of database triggers?

Three axes: timing (BEFORE the write, AFTER it, or INSTEAD OF it), event (insert, update, delete — some systems add schema and login events), and granularity (row-level, firing per affected row, versus statement-level, firing once per statement). Application hooks are effectively row-level BEFORE and AFTER triggers.

What is the difference between BEFORE and AFTER triggers?

Capability and timing. BEFORE runs ahead of the write and can validate, modify the incoming data, or abort the operation entirely. AFTER runs once the write succeeded — it cannot change what happened, only react: log it, count it, notify about it. AFTER never fires for writes that failed.

What is the difference between a trigger and a stored procedure?

Invocation. A stored procedure is called explicitly, takes parameters, and returns results. A trigger is never called — it fires automatically when its event occurs, parameterless, attached to a table. Same procedural machinery, opposite activation model.

Should logic live in triggers or application code?

The consensus is a hybrid: triggers (or hooks) for rules that must hold on every write path — validation, integrity, audit — and application services for complex workflows. Application-level hooks are the modern middle ground: trigger guarantees, real programming language, version control.

When should you use triggers?

Audit trails, integrity rules that must bind every client, computed and denormalized fields, timestamps, cascading cleanups. The discipline that keeps them healthy: short, simple, fast, and independent of deep business logic — a trigger is a rule, not a workflow engine.

Do triggers hurt performance?

They run synchronously inside the write path, so a slow trigger slows every save, and row-level triggers multiply on bulk operations — a hundred-thousand-row update fires a hundred thousand times. Keep before-hooks lean and push slow reactions to after-hooks or background jobs.

Can a trigger cause an infinite loop?

Famously — a trigger that writes to its own table re-fires itself, and an afterSave that saves the object it just handled recurses until something breaks. Guard by checking what actually changed before writing, and never re-save the triggering object from its own after-hook without a terminating condition.

Can triggers call external services?

SQL triggers essentially cannot — and should not — reach outside the database. This is the marquee advantage of application-level hooks: an afterSave in JavaScript can send a push notification, call any API, or fire an outgoing webhook, because it runs in the backend process with the full ecosystem available.

What is beforeSave used for?

The three jobs that require running before the write: validation (throw an error and the save fails, for every client), normalization (trim, truncate, canonicalize fields), and defaults or computed values. Side effects do not belong there — if the save later fails, the side effect already happened.

Related terms

Compare with

Further reading

Ready to build your backend?

Start your project on Back4app in minutes — database, auth, APIs, and Cloud Code included. No credit card required.

Written and reviewed by Back4app Engineering, Back4app Engineering · Published 2026-07-30