What is the Trigger Handler pattern in Salesforce?

Overview

The Trigger Handler pattern is a design approach used in Salesforce Apex to keep triggers thin and maintainable by delegating processing logic to a separate handler class. It enforces single responsibility, improves testability, and ensures Apex triggers remain bulkified and organized — essential for production-grade Salesforce orgs.

Why use the Trigger Handler pattern?

Using a trigger handler helps you follow Salesforce best practices and keywords like “Trigger Handler pattern”, “Apex triggers”, “bulkification”, “single responsibility principle”, and “trigger frameworks”. Benefits include:

  • One trigger per object (delegates work to handler)
  • Cleaner, more readable triggers
  • Centralized business logic for easier testing and reuse
  • Better bulk processing and governor limits control
  • Clear separation of trigger-context-specific code from domain/business logic

Core concepts

Typical elements of the Trigger Handler pattern:

  • Slim Trigger: The trigger only determines the context (before/after, insert/update/delete) and calls methods on the handler.
  • Handler Class: A class (e.g., AccountTriggerHandler) that implements methods like beforeInsert, afterUpdate, beforeDelete, etc.
  • Bulkified Methods: Handler methods accept collections (List, Map) rather than single records to support bulk operations.
  • Context Guarding: Use Trigger.isBefore, Trigger.isInsert, Trigger.isUpdate, Trigger.isDelete and maps/lists provided by the Trigger object.
  • Optional Frameworks: Some teams implement a base TriggerHandler abstract class or a TriggerContext wrapper to standardize behavior (prevent recursion, manage static state, order of operations).

Simple example

Below is a minimal implementation showing a thin trigger that delegates to a handler class.

// AccountTrigger.trigger
trigger AccountTrigger on Account (before insert, before update, after insert, after update, before delete) {
if (Trigger.isBefore) {
if (Trigger.isInsert) AccountTriggerHandler.beforeInsert(Trigger.new);
if (Trigger.isUpdate) AccountTriggerHandler.beforeUpdate(Trigger.new, Trigger.oldMap);
if (Trigger.isDelete) AccountTriggerHandler.beforeDelete(Trigger.oldMap);
}
if (Trigger.isAfter) {
if (Trigger.isInsert) AccountTriggerHandler.afterInsert(Trigger.newMap);
if (Trigger.isUpdate) AccountTriggerHandler.afterUpdate(Trigger.newMap, Trigger.oldMap);
}
}

// AccountTriggerHandler.cls
public with sharing class AccountTriggerHandler {
public static void beforeInsert(List newList) {
// perform validations or defaulting - bulkified
for (Account a : newList) {
if (String.isBlank(a.AccountNumber)) {
a.AccountNumber = 'AUTO-' + Datetime.now().getTime();
}
}
}

public static void beforeUpdate(List newList, Map oldMap) {
// compare old and new values using oldMap - bulkified
}

public static void afterInsert(Map newMap) {
// perform async work, future/batch/queueable calls
}

public static void afterUpdate(Map newMap, Map oldMap) {
// post-processing
}

public static void beforeDelete(Map oldMap) {
// cleanup or prevent delete logic
}
}

Best practices and patterns

  • Use one trigger per SObject and delegate to a handler class for all contexts.
  • Keep handler public static entry points that accept collections for bulk safety.
  • Separate concerns: validation, record updates, callouts, and complex business logic should be in different private/helper methods or classes.
  • Respect governor limits: avoid SOQL/DML inside loops; batch queries and DML operations.
  • Prevent recursion: use a static Boolean flag or a more robust TriggerContext manager to avoid infinite loops.
  • Write focused unit tests for handler methods and simulate Trigger context using Test methods and Test.startTest()/Test.stopTest().
  • Consider using or building a lightweight trigger framework (BaseTriggerHandler) for consistent lifecycle hooks (beforeValidate, beforeSave, afterCommit, etc.).

Common extensions

Advanced teams often extend the pattern with:

  • TriggerContext objects that expose common properties (isInsert, isUpdate, newList, oldMap) and helper methods.
  • Handler base classes with overridable lifecycle methods so child handlers only implement needed stages.
  • Centralized exception handling and custom logger utilities for easier debugging.

Conclusion

The Trigger Handler pattern is an essential architectural practice in Salesforce development. It enforces clean, testable, and bulk-safe triggers by isolating trigger orchestration from the actual business logic. For teams building scalable Salesforce apps, adopting this pattern (or a variant/framework) reduces technical debt and simplifies maintenance.