top of page
Your long-standing Salesforce Consultants

Salesforce Trigger Framework (Code Version): A Recursion-Safe, Bulk-Safe, Scalable Architecture (Solve SOQL 101 error and DML 151 Error)

  • Writer: Deepak Arora
    Deepak Arora
  • 4 minutes ago
  • 3 min read

In our previous post, we shared the story of how we encountered trigger recursion, SOQL limit breaches, and performance issues when multiple automation layers (Flows, PB, Workflows, Integrations) acted on the same records.


As promised, here is the complete code version of the recursion-safe, enterprise-ready Salesforce Trigger Framework we implemented to solve this problem.


This design keeps the trigger clean, avoids repeated execution on the same records, separates logic by context (insert/update/delete), and ensures governor-limit-safe execution in every scenario.


Core Principles of the Framework

Before jumping into the code, here are the design principles:

  • Single Trigger Per Object

No mixing logic across multiple triggers.

  • Switch-based Context Handling

Clear separation for before insert, before update, after update, etc.

  • Recursion Control

Prevent the same record from being processed twice inside the same

transaction.

  • Bulk-Safe Design

All logic runs on lists/maps—no per-record SOQL or DML.

  • Cleaner Maintenance

Each event has its own handler method.


The Trigger (Generic, Clean, Structured)

trigger ObjName_trigger on ObjName (before delete, after delete, before
                      insert, after insert, before update, after update) {
    Switch on Trigger.operationType { 
        when BEFORE_INSERT {
            if (trigger_cntrl.is_before_insert) {
				trigger_cntrl.beforeInsert(Trigger.New,Trigger.Old,
                                           Trigger.Newmap,Trigger.Oldmap);
            }
        }
        when BEFORE_UPDATE        {
            list<Obj> list_of_new_obj = new list<Obj>();
            map<id,Obj> new_map_of_obj = new map<id,Obj>();
            for(Obj current_Obj : trigger.new) {
                if(!trigger_cntrl.set_b_update.contains(current_Obj.id)){
                    list_of_new_obj.add(current_Obj);
                    new_map_of_obj.put(current_Obj.id,current_Obj);
                    trigger_cntrl.set_b_update.add(current_Obj.id);
                }
            }
            if(!list_of_new_obj.isempty()){ 
				trigger_cntrl.beforeupdate(list_of_new_obj,Trigger.Old,
									 new_map_of_obj,Trigger.Oldmap);
            }
        }
        when AFTER_INSERT {
            list<Obj> list_of_new_Obj = new list<Obj>();
            map<id,Obj> new_map_of_Obj = new map<id,Obj>();
            for(Obj current_Obj : trigger.new) {
			if(!trigger_cntrl.set_a_insert.contains(current_Obj.id)){
                    list_of_new_Obj.add(current_Obj);
                    new_map_of_Obj.put(current_Obj.id,current_Obj);
                    trigger_cntrl.set_a_insert.add(current_Obj.id);
                }
            }
            if(!list_of_new_Obj.isempty()) {
                trigger_cntrl.afterInsert(list_of_new_Obj,Trigger.Old,
                                      new_map_of_Obj,Trigger.Oldmap);
            }
        }
        when AFTER_UPDATE {
            list<Obj> list_of_new_Obj = new list<Obj>();
            map<id,Obj> new_map_of_Obj = new map<id,Obj>();
            for(Objcurrent_Obj : trigger.new){
			if(!trigger_cntrl.set_a_update.contains(current_Obj.id)){
                    list_of_new_obj.add(current_obj);
                    new_map_of_obj.put(current_obj.id,current_obj);
				  trigger_cntrl.set_a_update.add(current_obj.id);
                }
            }
            if(!list_of_new_obj.isempty()){
				trigger_cntrl.afterUpdate(list_of_new_obj,Trigger.Old,
									new_map_of_obj,Trigger.Oldmap);
            }
        }
        when BEFORE_DELETE
        {            
			trigger_cntrl.beforedelete(Trigger.New,Trigger.Old,
								Trigger.Newmap,Trigger.Oldmap);
        }
    }
}

The Controller Class (Recursion-Safe Handler)

Below is the clean controller class that receives filtered lists/maps and contains dedicated methods for each trigger context.

public class Account_trigger_cntrl {
    public static Boolean is_before_insert = true;
    public static Boolean is_after_insert = true;
    public static Set<Id> set_a_insert = new Set<Id>();
    public static Set<Id> set_a_update = new Set<Id>();
    public static Set<Id> set_b_update = new Set<Id>();
    public static void beforeInsert(list<obj> new_list_obj, list<obj>
         old_list_obj, map<id,obj> new_map_obj, map<id,obj> old_map_obj)
    {
        system.debug('inside before insert trigger');
    }
    public static void beforeupdate(list<obj> new_list_obj, list<obj> 
         old_list_obj, map<id,obj> new_map_obj, map<id,obj> old_map_obj)
    {
        system.debug('inside before update trigger');
    }
    public static void beforedelete(list<obj> new_list_obj, list<obj> 
         old_list_obj, map<id,obj> new_map_obj, map<id,obj> old_map_obj)
    {
        system.debug('inside before delete trigger');
    }
    public static void afterInsert(list<obj> new_list_obj, list<obj>   
         old_list_obj, map<id,obj> new_map_obj, map<id,obj> old_map_obj)
    {
        system.debug('inside after Insert trigger');
    }
    public static void afterUpdate(list<obj> new_list_obj, list<obj> 
          old_list_obj, map<id,obj> new_map_obj, map<id,obj> old_map_obj)
    {
        system.debug('inside after update trigger');
    }

Why This Framework Works

  • Prevents Multiple Executions

Records are processed only once using the recursion set.


  • Supports Heavy Automation

    Flows + PB + Apex + Integrations all work without causing trigger loops.


  • Bulk Friendly

All logic is list-based and executes safely during bulk record operations.


  • Maintainable & Expandable

Each method corresponds to a specific event, making enhancements easy.


  • Stable in Production

No more SOQL 101 error No more DML 151 error in trigger. No more

unpredictable recursion.


ree

🎯Final Thoughts

This trigger framework has become our default structure for all Salesforce implementations. It ensures:

  • Predictable behavior

  • No unexpected recursion

  • Clean separation of logic

  • Full alignment with governor limits

  • Easy debugging and future enhancements

If your org is scaling or using multiple automation layers, adopting a framework like this is not optional—it’s essential.

 
 
 

Comments


bottom of page