Trigger Framework Patterns
Overview
A trigger framework enforces consistent, bulk-safe execution by separating trigger entry points from business logic.
The goal is predictable behavior across insert/update/delete contexts with minimal duplicate logic.
Consensus Best Practices
- One trigger per object.
- Trigger contains routing only; no business logic.
- Handler methods separated by context.
- Service/domain layers own business behavior.
- Guard against recursion and duplicate processing.
- Test each context path explicitly.
Core Structure
trigger ContactTrigger on Contact (
before insert, before update,
after insert, after update
) {
ContactTriggerHandler.run(Trigger.new, Trigger.oldMap, Trigger.isBefore, Trigger.isInsert);
}
public with sharing class ContactTriggerHandler {
public static void run(List<Contact> newList, Map<Id, Contact> oldMap, Boolean isBefore, Boolean isInsert) {
if (TriggerGate.shouldSkip('ContactTrigger')) return;
if (isBefore && isInsert) handleBeforeInsert(newList);
else if (isBefore && !isInsert) handleBeforeUpdate(newList, oldMap);
else if (!isBefore && isInsert) handleAfterInsert(newList);
else if (!isBefore && !isInsert) handleAfterUpdate(newList, oldMap);
}
private static void handleBeforeInsert(List<Contact> rows) { /* validation */ }
private static void handleBeforeUpdate(List<Contact> rows, Map<Id, Contact> oldMap) { /* diff checks */ }
private static void handleAfterInsert(List<Contact> rows) { /* enqueue async */ }
private static void handleAfterUpdate(List<Contact> rows, Map<Id, Contact> oldMap) { /* side effects */ }
}
Recursion and Re-Entry Control
Use a small gate utility keyed by handler/context to avoid duplicate execution in one transaction.
Avoid global static booleans that accidentally suppress valid processing.
Change Detection Pattern
Only execute expensive logic when relevant fields changed:
- compare
Trigger.newvsTrigger.oldMap, - isolate diff checks into helper methods,
- keep DML and callouts out of unconditional paths.
Common Failure Modes
- Multiple triggers on same object with conflicting order.
- SOQL/DML in loops hidden inside handler helpers.
- Async fan-out in after triggers without throttling.
- Overbroad recursion guards that block valid operations.