Events

System hook events, what triggers them, and what your handler is expected to return.

Filenames use kebab case (before-save.ts); the underlying event name uses underscores (before_save). They’re equivalent.

EventFileTriggered byReturn value
before_savebefore-save.tsEntity create / update, before commit.An object of fields to change, merged into the entity. null to skip. Throw to reject the write.
after_saveafter-save.tsEntity create / update, after commit.Ignored — side-effect only.
before_postbefore-post.tsThe “post” workflow transition (e.g., posting an invoice to the ledger), before commit.Throw to abort.
after_postafter-post.tsSame workflow, after commit.Ignored.
before_deletebefore-delete.tsEntity destroy, before commit.Throw to abort.
after_deleteafter-delete.tsEntity destroy, after commit.Ignored.
before_approvebefore-approve.tsApproval transition, before commit.Throw to abort.
after_approveafter-approve.tsApproval transition, after commit.Ignored.
recalculaterecalculate.tsDraft recalculation while a user edits — fires synchronously on changes.Calculation components and warnings. See Hooks → Recalculate.

before_post, before_approve, and the matching after_* events only fire on entities whose workflow includes a post or approve transition (Invoice, Bill, JournalEntry, etc. — not all entities have these). If your hook isn’t firing, that’s the first thing to check.

Hook context

Save hooks receive a HookContext:

interface HookContext<T = any> {
  entity: T;             // entity AFTER the change (or proposed entity, in before-*)
  entityType: string;
  operation: 'create' | 'update' | 'delete';
  previous: T | null;    // entity before; null on create
  changes: string[];     // names of fields actually modified
}

previous is null on create. changes lists field names — useful for short-circuiting in after_save:

if (!ctx.changes.includes("status")) return;

The recalculate hook’s context is different — see Hooks → Recalculate.