Skip to main content
New tool CRON Expression Builder — preview next run times before you schedule Apex. Open the builder →
Apex

Types of Apex Triggers in Salesforce: Before, After & 7 Trigger Events

Apex triggers come in two timing variants (before and after) and seven event types. Knowing which to use prevents recursion, governor-limit errors, and the dreaded 'cannot reference deleted object' bugs.

If you're more than a week into Apex you've probably written a trigger. But the difference between a maintainable trigger and a fragile one usually comes down to picking the right type — the right combination of timing (before / after) and event (insert / update / delete / undelete). This guide walks through all seven, the order they execute in, and the patterns that turn ad-hoc triggers into a clean trigger framework.

The 7 trigger events at a glance

Timing Event Has record Id? Can modify same record without DML? Common use
Before Insert No (yet) Yes Default field values, normalize input
Before Update Yes Yes Validate / transform field changes
Before Delete Yes N/A — record is going away Block deletion, archive snapshot
After Insert Yes (new) No (needs DML) Update related records, send notifications
After Update Yes No Cross-object roll-ups, audit log
After Delete Yes (read-only) N/A Cleanup of orphan child records
After Undelete Yes No Restore counters / index entries

Before vs after — the right mental model

Before triggers intercept the record on its way to the database. You hold the SObject in memory, you can change its fields directly, and the modified version is what gets saved — no extra DML required:

trigger AccountBefore on Account (before insert, before update) {
  for (Account a : Trigger.new) {
    if (a.Name != null) {
      a.Name = a.Name.trim();  // No DML needed; the saved record has trimmed name
    }
  }
}

After triggers see the record after it's persisted. Inserts now have an Id (useful for creating child records), but to modify the same record you need a separate update DML — which fires another trigger pass:

trigger ContactAfter on Contact (after insert) {
  // Use Trigger.new — every contact now has an Id
  List<Account> updates = new List<Account>();
  for (Contact c : Trigger.new) {
    updates.add(new Account(Id = c.AccountId, Last_Contact_Created__c = Date.today()));
  }
  if (!updates.isEmpty()) update updates;
}

The mental rule: modifying the same record? before. Modifying related records? after.

System.TriggerOperation: the modern dispatch pattern

The legacy way to branch trigger logic was a chain of booleans:

if (Trigger.isBefore && Trigger.isInsert) { ... }
else if (Trigger.isAfter && Trigger.isUpdate) { ... }

The modern way uses the System.TriggerOperation enum:

trigger AccountTrigger on Account (
  before insert, before update,
  after insert, after update, after delete
) {
  switch on Trigger.operationType {
    when BEFORE_INSERT { AccountHandler.handleBeforeInsert(Trigger.new); }
    when BEFORE_UPDATE { AccountHandler.handleBeforeUpdate(Trigger.new, Trigger.oldMap); }
    when AFTER_INSERT  { AccountHandler.handleAfterInsert(Trigger.new); }
    when AFTER_UPDATE  { AccountHandler.handleAfterUpdate(Trigger.new, Trigger.oldMap); }
    when AFTER_DELETE  { AccountHandler.handleAfterDelete(Trigger.old); }
    when else          { /* no-op */ }
  }
}

The switch is more readable, exhaustive (the compiler nudges you toward handling every case), and pairs naturally with the trigger framework pattern below.

One trigger per object: the framework pattern

Salesforce permits multiple triggers per object, but the execution order is unspecified — which means a debugging nightmare. Best practice is one trigger per object that delegates to a handler class:

// AccountTrigger.trigger — only one per object
trigger AccountTrigger on Account (
  before insert, before update, before delete,
  after insert, after update, after delete, after undelete
) {
  new AccountHandler().run();
}

// AccountHandler.cls — testable, recursion-safe
public class AccountHandler {
  private static Boolean isRunning = false;

  public void run() {
    if (isRunning) return;          // recursion guard
    isRunning = true;
    try {
      switch on Trigger.operationType {
        when BEFORE_INSERT { handleBeforeInsert(Trigger.new); }
        when BEFORE_UPDATE { handleBeforeUpdate(Trigger.new, Trigger.oldMap); }
        when AFTER_INSERT  { handleAfterInsert(Trigger.new); }
        // ...
      }
    } finally {
      isRunning = false;
    }
  }
  // private handler methods...
}

Three things this gives you:

  1. Unit-testable handlers. Call new AccountHandler().handleBeforeInsert(records) directly from a test class without going through a real DML.
  2. Recursion control. A single static flag prevents reentry within the same transaction.
  3. One known dispatch point per object. No mystery ordering between competing triggers.

The full order of execution (you'll wish you knew this earlier)

When a record is saved, this is what happens:

  1. System validation rules (required fields, lookup integrity)
  2. Before triggers fire
  3. Custom validation rules
  4. Record commits to the buffer (not yet to the DB)
  5. After triggers fire
  6. Assignment rules run
  7. Auto-response rules run
  8. Workflow field updates run — these can re-fire steps 2–8 once via a "post-update" cycle
  9. Process Builder / Flow trigger automations run
  10. Roll-up summary recalculations
  11. Criteria-based sharing rules
  12. DB commit happens
  13. Post-commit logic: emails, async Apex (@future, Queueable, Schedulable), Platform Events

This is why a trigger sometimes appears to fire twice for one DML — workflow field updates re-trigger the save. Use the recursion guard pattern above to keep your handler logic idempotent. See Salesforce Order of Execution for the full reference with edge cases.

Common mistakes

  • DML inside the loop. Always batch updates outside the for-loop.
  • Forgetting Trigger.oldMap on update/delete events. It's the only way to compare old vs new values.
  • Mutating Trigger.new in after triggers. It's read-only after the save. Use before triggers for self-modification.
  • No recursion guard. Workflow field updates will silently re-fire your trigger.
  • Multiple triggers per object. Pick one; route everything through it.

Pick the right timing (before for self-edits, after for cross-record), the right event (one of the seven listed above), use System.TriggerOperation for dispatch, and route everything through a handler class with a recursion guard. That's 95% of the trigger discipline you'll ever need.

Frequently asked questions

How many types of Apex triggers are there in Salesforce?

Apex supports 7 trigger events organized into 2 timing categories. Before triggers (3): before insert, before update, before delete. After triggers (4): after insert, after update, after delete, after undelete. A single trigger declaration can listen to any combination of these events.

What is the difference between before and after triggers?

Before triggers fire BEFORE the record is saved to the database — you can modify field values directly on the record being saved without an extra DML. After triggers fire AFTER the save commits — the record now has an Id (useful for inserts) but you can't modify the same record without a separate DML. Use before for self-record edits, after for cross-record operations.

What is System.TriggerOperation in Apex?

System.TriggerOperation is an enum representing the current trigger event. Values: BEFORE_INSERT, BEFORE_UPDATE, BEFORE_DELETE, AFTER_INSERT, AFTER_UPDATE, AFTER_DELETE, AFTER_UNDELETE. Use it in switch statements: switch on Trigger.operationType { when AFTER_INSERT { ... } }. Replaces the older Trigger.isBefore + Trigger.isInsert boolean checks with a cleaner enum-based pattern.

When should I use a trigger framework in Salesforce?

Use a trigger framework as soon as you have more than 2-3 triggers per object or any cross-trigger orchestration needs. Frameworks (like FFLib's TriggerHandler, Kevin O'Hara's pattern, or your own) provide: (1) one trigger per object — Salesforce best practice; (2) handler classes that are unit-testable; (3) recursion prevention via static control flags; (4) bypass mechanism for data loads.

Can a trigger fire on multiple events?

Yes. Declare them comma-separated: trigger MyTrigger on Account (before insert, before update, after insert, after update). Inside, branch by Trigger.operationType (or the legacy Trigger.isBefore/isInsert booleans) to handle each event distinctly. This is the standard one-trigger-per-object pattern.

What is the order of trigger execution in Salesforce?

Within a single save: (1) System validation runs first; (2) Before triggers fire; (3) Custom validation rules run; (4) Record saves to database (no commit yet); (5) After triggers fire; (6) Assignment rules, auto-response, workflow field updates run; (7) Process Builder/Flow trigger; (8) Roll-up summary fields recalculate; (9) DB commit; (10) Post-commit logic (emails, async Apex). Triggers can re-fire if workflow field updates re-trigger the save.

Can you have multiple triggers on the same Salesforce object?

Yes technically — Salesforce allows multiple triggers per object — but it's strongly discouraged. The execution order between multiple triggers on the same object is NOT guaranteed, which makes debugging impossible. Best practice: one trigger per object that delegates to handler classes (the 'trigger framework' pattern).

Newsletter

One email every Tuesday

New guides, tool updates, and the release-note changes that break things.

No spam. Unsubscribe in one click.

Comments

Loading comments...

Leave a Comment