Apex Trigger Frameworks — Which pattern should you actually use?
Raw triggers work on day one. They break on day ninety. Here's a practitioner's guide to the three frameworks you'll encounter in the wild — and exactly when to reach for each one.
The Problem
Why trigger architecture matters more than the trigger itself
Every Salesforce developer has been here: you inherit an org with four triggers on the Account object, zero order of execution control, recursive loops buried in helper classes, and a Tuesday deployment that broke the London team's pipeline for six hours.
The code in each trigger? Fine. The architecture holding them together? Non-existent.
The real cost of "just write a trigger": Salesforce doesn't guarantee execution order across multiple triggers on the same object. Without a framework, you're one update statement away from an infinite loop — or worse, silent data corruption.
Let's break down the three patterns you'll encounter — when each is appropriate, what the code actually looks like, and the decision matrix to make the call fast.
The Patterns
Three frameworks, three different use cases
Pattern 01 (AVOID IN PROD) - Raw Trigger (No Framework)
Logic lives directly in the trigger body. Fine for prototypes, sandboxes, or a quick proof-of-concept. In production, it becomes unmaintainable within weeks — no separation of concerns, impossible to unit test cleanly, and guaranteed to conflict with the next trigger someone adds. #AdminScripting #ScratchOrgsOnly
Pattern 02 (STANDARD CHOICE) - Trigger Handler Pattern
One trigger per object. All logic delegated to a handler class. This is the industry-standard baseline — used in 80%+ of well-maintained orgs. Gives you clean separation, testability, and prevents the multi-trigger chaos. Kevin O'Hara's open-source framework is the canonical implementation. #Developers #SolutionsArchitects
Pattern 03 (ENTERPRISE ONLY) - Enterprise Framework (TDTM / Custom)
Trigger Dispatch with Table-Driven Trigger Management. Logic is registered in custom metadata or custom objects — triggers are fully data-driven and configurable without code deployments. Used in NPSP, Healthcare Cloud, and large ISV packages. Significant overhead to set up; overkill for most implementations. #TechArhitects #ISVBuilders
Code walkthrough
What each pattern looks like in practice
Pattern 01 · AccountTrigger.trigger — Raw trigger (what NOT to do)
trigger AccountTrigger on Account (before insert, before update) {
// Logic directly in trigger — no separation of concerns
if (Trigger.isBefore && Trigger.isInsert) {
for (Account acc : Trigger.new) {
// Business logic mixed with trigger context
if (acc.Industry == 'Technology') {
acc.Rating = 'Hot';
}
}
}
// Imagine 3 more developers adding blocks here over 6 months...
}
Recommended by LinkedIn
Pattern 02 · AccountTrigger.trigger — Thin trigger
trigger AccountTrigger on Account (
before insert, before update, before delete,
after insert, after update, after delete, after undelete
) {
// That's it. One line. The handler does everything.
AccountTriggerHandler.run();
}
Pattern 02 · AccountTriggerHandler.cls — Handler class
public class AccountTriggerHandler extends TriggerHandler {
private List<Account> newAccounts;
private Map<Id, Account> oldAccountMap;
public AccountTriggerHandler() {
this.newAccounts = (List<Account>) Trigger.new;
this.oldAccountMap = (Map<Id, Account>) Trigger.oldMap;
}
@Override
protected void beforeInsert() {
AccountService.setDefaultRating(this.newAccounts);
}
@Override
protected void afterUpdate() {
AccountService.syncRelatedContacts(
this.newAccounts, this.oldAccountMap
);
}
}
Pattern 02 · Recursion guard — prevents infinite loops
public class AccountTriggerHandler extends TriggerHandler {
// Static flag: survives across trigger re-entries in same transaction
private static Boolean hasRun = false;
@Override
protected void afterUpdate() {
if (hasRun) return;
hasRun = true;
// Safe to update records here — won't re-fire infinitely
AccountService.syncRelatedContacts(Trigger.new, Trigger.oldMap);
}
}
Pro tip: Kevin O'Hara's TriggerHandler base class (free on GitHub) gives you built-in bypass logic, max loop count, and context detection. Use it as your base class rather than building from scratch. It's battle-tested across thousands of orgs.
Decision matrix
Pick your pattern in 30 seconds
Key takeaways
What to walk away with
Resources from this edition: github.com/kevinohara80/sfdc-trigger-framework — Kevin O'Hara's TriggerHandler base classSalesforce Docs: Trigger Best Practices → Apex Developer Guide → Triggers and Order of Execution
Playbook Thoughts
Most Salesforce orgs don't have a trigger problem.They have a trigger architecture problem. Identify issues in your current org and fix it.
Salesforce works best when decisions are intentional and execution is grounded in real experience. Each edition of Salesforce Playbooks shares practical patterns, honest insights, and lessons from the field—helping teams design Salesforce orgs that scale without unnecessary complexity.
Follow Salesforce Playbooks to stay ahead with insights you can apply immediately in your Salesforce org.
© Perigeon Software. All rights reserved.