Suppose you have been developing on Salesforce for a while. In that case, you have probably encountered the same nightmare: multiple triggers on a single object, business logic scattered across classes, and the occasional “Too many SOQL queries” error haunting your deployments.
Messy triggers are one of the biggest causes of technical debt in Salesforce orgs. As the project grows, so does the complexity, and without structure, even a small update can break production logic.
So, what’s the solution?
Apex Trigger Framework: a structured way to manage trigger logic that keeps your code clean, scalable, and easy to maintain.
In this post, we’ll break down how to design your own Apex Trigger Framework, step by step, with best practices and sample code you can use right away.
Table of Contents
What’s wrong with Traditional Triggers?
Before diving into the framework, let’s quickly look into which typically goes wrong in trigger-heavy orgs:
- Multiple triggers per object – no control over execution order.
- Business logic inside triggers – hard to read, debug, and test.
- No bulk handling– leads to governor limit errors.
- Copy-pasted code – changes in one place break others.
In short, no structure = chaos.
What Is an Apex Trigger Framework?
An Apex Trigger Framework is simply a design pattern that separates trigger logic from business logic.
Instead of writing logic directly inside the trigger, you route everything through handler classes. This makes your code modular, reusable, and testable.
Benefits:
- Enforces the “one trigger per object” rule.
- Promotes separation of concerns.
- Improves code reusability and readability.
- Makes testing and debugging easier.
- Supports bulk-safe operations.
Building a Simple Trigger Framework (Step-by-Step)
Let’s create a clean trigger setup for the Account object.
Step 1: Create the Trigger
Trigger AccountTrigger on Account(before insert, before update, before delete,
after insert, after update, after delete, after undelete){
AccountHandler.dispatch(AccountHandler.class);
}
Step 2: Base Handler (All logic lives here)
public abstract class TriggerBaseHandler {
public static void dispatch(Type handlerType) {
TriggerBaseHandler handler = (TriggerBaseHandler) handlerType.newInstance();
if (Trigger.isBefore) {
if (Trigger.isInsert) handler.beforeInsert(Trigger.new);
if (Trigger.isUpdate) handler.beforeUpdate(Trigger.new, Trigger.oldMap);
if(Trigger.isDelete) handler.beforeDelete(Trigger.old);
}
if (Trigger.isAfter) {
if (Trigger.isInsert) handler.afterInsert(Trigger.new);
if (Trigger.isUpdate) handler.afterUpdate(Trigger.new, Trigger.oldMap);
if(Trigger.isDelete) handler.afterDelete(Trigger.old);
if (Trigger.isUndelete) handler.afterUndelete(Trigger.new);
}
}
// Virtual methods allow child handlers to override only the needed events
public virtual void beforeInsert(List newList) {}
public virtual void beforeUpdate(List newList, Map oldMap) {}
public virtual void beforeDelete(List oldList) {}
public virtual void afterInsert(List newList) {}
public virtual void afterUpdate(List newList, Map oldMap) {}
public virtual void afterDelete(List oldList) {}
public virtual void afterUndelete(List newList) {}
}
Step 3: AccountHandler (Overrides only needed methods)
public with sharing class AccountHandler extends TriggerBaseHandler {
public static void dispatch(Type t){
TriggerBaseHandler.dispatch(t);
}
public override void beforeInsert(List newList) {
AccountService.beforeInsert((List) newList);
}
public override void beforeUpdate(List newList, Map oldMap) {
AccountService.beforeUpdate( (List) newList, (Map) oldMap);
}
public override void beforeDelete(List oldList){
AccountService.beforeDelete((List) oldList);
}
public override void afterInsert(List newList){
AccountService.afterInsert((List) newList);
}
public override void afterUpdate(List newList, Map oldMap){
AccountService.afterUpdate( (List) newList, (Map) oldMap);
}
public override void afterDelete(List oldList){
AccountService.afterDelete((List) oldList);
}
public override void afterUndelete(List newList) {
AccountService.afterUndelete((List) newList);
}
}
Step 4: Service class- (Put all business logic here)
public with sharing class AccountService {
public static void beforeInsert(List newList) {
// business logic here
}
public static void beforeUpdate(List newList, Map oldMap) {
// business logic here
}
public static void beforeDelete(List oldList){
// business logic here
}
public static void afterInsert(List newList){
// business logic here
}
public static void afterUpdate(List newList, Map oldMap){
// business logic here
}
public static void afterDelete(List oldList) {
// business logic here
}
public static void afterUndelete(List newList) {
// business logic here
}
This setup makes it easier to add consistent functionality across all your objects.
Advanced Enhancements:
Once you have the basics, you can enhance your framework with:
- Recursion control – prevent the same trigger from running twice.
- Custom metadata or custom settings – to control trigger activation.
- Centralized logging – track errors and trigger execution.
- Dependency injection – for improved testability.
Best Practices for Trigger Frameworks:
- One trigger per object.
- Never write business logic directly in the trigger.
- Keep triggers bulk-safe and governor-limit friendly.
- Use meaningful method and class names.
- Write unit tests for each handler method.
Conclusion:
A Trigger Framework isn’t just a coding pattern; It’s a mindset.
It helps you write cleaner, more predictable code that scales with your Salesforce org and keeps deployments painless.
If your current triggers are hard to read or constantly breaking, it’s time to refactor them using this approach. Start small, pick one object, and feel the difference in clarity and maintainability.
References:
[Apex Triggers Documentation]- https://developer.salesforce.com/docs/atlas.enus.apexcode.meta/apexcode/apex_triggers.htm
[Apex Triggers Best Practices] – https://developer.salesforce.com/docs/atlas.enus.apexcode.meta/apexcode/apex_triggers_bestpract.htm
[Define Triggers Help]- https://help.salesforce.com/s/articleViewid=platform.code_define_trigger.htm&type=5
[Trigger Context Variables]- https://developer.salesforce.com/docs/atlas.enus.apexcode.meta/apexcode/apex_triggers_context_variables.htm
Most Reads:
- Build a Dynamic, Reusable Lightning Datatable in Salesforce LWC (With Metadata-Driven Columns, Search & Pagination)
- Beyond Triggers: The Apex Developer’s New Job in the Age of AI
- Agentforce Explained: The New Era of AI Agents Inside Salesforce
- Salesforce Marketing Cloud to Agentforce: The Future of Marketing Automation
- How to Create a WhatsApp Business Channel and Configure It in Meta Business Suite
Resources
- [Salesforce Developer]- (Join Now)
- [Salesforce Success Community] (https://success.salesforce.com/)
For more insights, trends, and news related to Salesforce, stay tuned with Salesforce Trail

Ritika Goel
I’m a Salesforce Developer who enjoys turning complex business requirements into clean, scalable solutions. I specialize in Apex, Lightning Web Components, Flows, and Salesforce integrations. My focus is always on writing optimized, bulkified, and maintainable code that follows industry best practices.
- This author does not have any more posts.


