Skip to content

Execution Order & Cost

What one TriggerOrchestrator.run(…) does, in order, and what it costs.

One Run per Chunk

The platform fires a trigger once per context for each chunk of up to 200 records. Each firing is one run. An insert of 201 records makes four runs: before insert and after insert for the first 200 records, then both again for the last one.

Each run calls <ctx>Handlers(), loads parents, runs providers and commits again. Handlers created in <ctx>Handlers() start with empty instance fields. Static fields, TriggerOrchestrator.bypass() switches and recursion counts last the whole transaction.

Order of a Run

  1. Run switches. TriggerOrchestrator.bypass() (all(), sObject(…), orchestrator(…)) or a checked TriggerObject__mdt.Bypass__c ends the run before any of your code. So does an orchestrator that does not implement the context.
  2. Handler list. <ctx>Handlers() runs. A class that implements two roles runs only as the first: Populator over Validator, Writer over Dispatcher.
  3. Handler switches. bypass().handler(…), then TriggerHandler__mdt.Bypass__c, then bypassOn<Ctx>When(). A skipped handler declares no parents.
  4. Parents load. One load for all remaining handlers, before the first handler runs.
  5. Each handler runs, in list order.
    • Its RelatedQuery providers run over all records.
    • Per record, the predicate decides and the action runs. A Dispatcher collects the records and dispatches once.
    • The Finalizer runs when at least one record qualified.
    • A Writer with OwnUnitOfWork or ContinueOnError commits its own unit of work.
  6. Shared commit. In after contexts, the shared unit of work commits once.
  7. Logger. The outermost run calls finalize() on the org's Logger.

Query Cost

SOQL queries a run spends outside your code:

SourceSOQL queries
Bypass metadata0, once per transaction
Logger discovery1, once per transaction
Parents in before contexts and after delete1 per declared lookup, per run
Parents in after insert, update and undelete1 on the trigger object per run, plus 1 per lookup it missed and, in after update, per PriorParentQuery lookup
Parents after a Populator1 per lookup re-pointed to a parent not loaded yet
RelatedQuery providerswhat each query runs, per handler
  • Declarations merge. Handlers that declare the same lookup share one query.
  • Parents and providers run before the first predicate, even when no record qualifies.
  • Never query in a predicate or an action. They run once per record. Declare a ParentQuery or a RelatedQuery, or query once in a Finalizer.

DML Cost

  • Before insert and before update: none. DML or an event publish throws.
  • Shared unit: one commit per run. It costs one statement per operation and object type. An empty unit costs nothing.
  • A Writer with OwnUnitOfWork or ContinueOnError: one more commit per run, when a record qualified.
  • Direct DML in a Dispatcher, a before delete handler or a Finalizer costs what it runs.
  • Events: a Publish After Commit event counts as a DML statement. A Publish Immediately event counts toward Limits.getPublishImmediateDML().

See Unit of Work.

Nested Runs

A commit or direct DML fires the triggers of the saved records at once. Each of them is a full nested run with its own parents, providers and commit.

  • Recursion counts are shared. In the update contexts, a Populator, Writer or Dispatcher skips a record after acting on it 3 times in the transaction. Change the limit with a RecursionGuard.
  • Only the outermost run calls finalize().
  • Salesforce allows 16 levels. The next one fails with "maximum trigger depth exceeded".