Salesforce Dictionary - Free Salesforce GlossarySalesforce Dictionary
Full Apex Triggers entry
How-to guide

How to write a maintainable Apex Trigger

Writing an Apex trigger is mechanically simple. Writing one that survives production data volumes, bulk operations, and three years of evolution is harder. Follow the conventions: one trigger per object, thin dispatcher with a handler class, bulkified logic, recursion control, and comprehensive tests. Skip any of these and the trigger becomes a debugging hotspot.

By Dipojjal Chakrabarti · Founder & Editor, Salesforce DictionaryLast updated May 16, 2026

Writing an Apex trigger is mechanically simple. Writing one that survives production data volumes, bulk operations, and three years of evolution is harder. Follow the conventions: one trigger per object, thin dispatcher with a handler class, bulkified logic, recursion control, and comprehensive tests. Skip any of these and the trigger becomes a debugging hotspot.

  1. Confirm Flow cannot do the job first

    Record-triggered Flow handles most save-event logic now. Use Apex triggers only when the logic exceeds Flow's expressiveness or performance envelope. This conversation with the architect saves weeks of rework later.

  2. Create the trigger file with all events declared

    One trigger per object, declaring all relevant events at once: trigger AccountTrigger on Account (before insert, after insert, before update, after update, before delete, after delete, after undelete). This is the convention and the entry point.

  3. Build the handler class with one method per context

    Separate file: AccountTriggerHandler.cls. Public methods for each event-context pair (handleBeforeInsert, handleAfterUpdate). The trigger delegates to these methods based on Trigger.isBefore, Trigger.isInsert, etc.

  4. Bulkify the logic with collections and maps

    Inside each handler method, collect record IDs, run SOQL outside loops, build maps of related data, process each Trigger.new record using the maps, then do DML outside the loop. This is the single most important discipline.

  5. Add recursion control via static boolean or framework

    Add a static boolean to the handler class (alreadyRan = false). Check it at entry to skip re-execution. For more complex needs, adopt a TriggerHandler framework like Hari Krishnan or Kevin Poorman patterns.

  6. Write the test class with bulk and edge cases

    Create AccountTriggerHandlerTest.cls. Use @testSetup for common data. Test the bulk case (insert 200 records). Test single-record cases. Test boundary conditions. Use System.assertEquals on the expected post-save state.

  7. Deploy with the test class and confirm coverage

    Deploy trigger and test class together. Confirm coverage above 75 percent. Run the tests in production after deployment to verify behavior matches sandbox. Set up a CI/CD pipeline that runs tests on every change.

  8. Monitor the trigger via Apex Jobs and exception logs

    Setup > Apex Jobs shows recent execution. Setup > Email Logs shows uncaught exceptions. Build a Custom Error_Log object and log caught exceptions to it for better debuggability than the default exception email.

Trigger Eventsremember

before insert, after insert, before update, after update, before delete, after delete, after undelete. Declare all events in one trigger per object.

Handler Classremember

Separate Apex class that holds the actual logic. The trigger should be a thin dispatcher that delegates to the handler.

Recursion Controlremember

Static boolean in the handler class or a TriggerHandler framework. Without it, save-cascade scenarios re-fire triggers infinitely.

Gotchas
  • Multiple triggers on the same object execute in non-deterministic order. Adopt the one-trigger-per-object convention from day one to avoid hard-to-debug ordering issues later.
  • Triggers fire on batches of up to 200 records. SOQL or DML inside a loop hits governor limits on the first bulk operation. Bulkification is mandatory, not optional.
  • Triggers can fire themselves recursively when their logic updates records they match. Use static booleans or a TriggerHandler framework to prevent infinite loops.
  • Before triggers can modify Trigger.new directly; the changes save automatically. After triggers cannot modify Trigger.new because the records are already saved.
  • Test coverage minimum is 75 percent across all Apex, including triggers. Write tests with meaningful assertions, not empty methods that just exercise lines for coverage credit.

See the full Apex Triggers entry

Apex Triggers includes the definition, worked example, deep dive, related terms, and a quiz.