Introduction
Triggers are a powerful mechanism in Salesforce to run custom Apex logic before or after data changes. But with great power comes the risk of hitting governor limits or creating unmaintainable code. This post summarizes best practices and key considerations when designing and implementing Apex triggers.
Core Best Practices
1. One Trigger Per Object
Keep a single trigger per object and delegate logic to handler classes. This avoids unpredictable order-of-execution issues and makes the trigger surface easy to manage.
2. Use a Trigger Handler / Framework
Implement a handler class (or lightweight framework) that routes actions by context (before insert, after update, etc.). This promotes separation of concerns and testability.
// Simple trigger pattern
trigger AccountTrigger on Account (before insert, before update, after insert, after update, after delete, after undelete) {
AccountTriggerHandler handler = new AccountTriggerHandler(Trigger.new, Trigger.oldMap);
if (Trigger.isBefore) {
if (Trigger.isInsert) handler.beforeInsert();
if (Trigger.isUpdate) handler.beforeUpdate();
} else if (Trigger.isAfter) {
if (Trigger.isInsert) handler.afterInsert();
if (Trigger.isUpdate) handler.afterUpdate();
if (Trigger.isDelete) handler.afterDelete();
if (Trigger.isUndelete) handler.afterUndelete();
}
}
// Handler sketch
public with sharing class AccountTriggerHandler {
private List
private Map
public AccountTriggerHandler(List
this.newList = newList;
this.oldMap = oldMap;
}
public void beforeInsert() {
// validation, defaulting, set values
}
public void beforeUpdate() {
// change detection, prevent recursion
}
public void afterInsert() {
// non-critical async operations or callouts via Queueable
}
}
3. Bulkify Everything
Always assume triggers can be invoked with batches (up to 200 records). Use collections (List/Set/Map) and avoid performing SOQL/DML inside loops. Consolidate queries and DML operations.
4. Minimize SOQL and DML
Group SOQL queries and DML statements. Use Maps to join data in-memory and reduce repeated queries. Watch combined governor limits across synchronous operations.
5. Avoid Hardcoding and Use Config
Use Custom Settings or Custom Metadata Types for configuration (thresholds, flags, integration endpoints) instead of hardcoding values or record IDs.
6. Prevent Recursion
Use static variables in handler classes or a dedicated recursion guard (per-transaction cache) to avoid infinite loops from update-triggered updates.
7. Handle Bulk Error Reporting
Use Database.insert/update with allOrNone=false when appropriate and capture Database.SaveResult to report per-record errors without failing the entire batch. Be conscious of transactional guarantees.
8. Prefer Queueable or Future for Long-Running Work
Offload non-critical or callout work to asynchronous Apex (Queueable, @future, Batchable) to keep trigger runtime short and avoid limit exhaustion.
9. Respect Sharing and Security
Use ‘with sharing’ in handler classes where business logic must enforce user permissions. For system-level operations, consider ‘without sharing’ carefully and document the reason.
10. Write Focused Unit Tests
Cover positive and negative scenarios, bulk scenarios (200 records), and mixed success cases. Use Test.startTest/Test.stopTest around async job invocations. Aim for maintainability and meaningful assertions.
Design & Operational Considerations
Order of Execution
Understand Salesforce order of execution (validation rules, before triggers, after triggers, workflow, processes, escalation rules, etc.). This helps when coordinating behavior with Process Builder/Flows and avoiding conflicts.
Coordination with Declarative Automation
Prefer declarative automation (Flow Builder) for simple record updates. If both Flow and Apex will run, clearly document the dependencies to avoid unexpected results.
Governors and Limits
Keep an eye on CPU time, heap size, number of SOQL queries (100 synchronous), number of DML statements (150), and callouts. Use tooling and logs to profile trigger execution under realistic data volumes.
Idempotency and Partial Failures
Design triggers to be idempotent when possible (safe to run multiple times) and to handle partial failures gracefully. When calling external systems, prefer queuing approaches and compensating transactions.
Monitoring and Alerts
Log exceptions (use a centralized logging pattern) and set up alerts for high error rates. Consider using Platform Events or custom objects to track failed integrations.
Checklist Before Deploying a Trigger
- One trigger per object implemented
- Trigger logic delegated to handler classes
- Bulk tests written (single, bulk, mixed)
- SOQL/DML reviewed for loops and limits
- Recursion guards in place
- Async work moved out of synchronous path
- Configuration via Custom Metadata / Settings
- Security and sharing reviewed
- Monitoring/logging enabled
Conclusion
Well-designed Apex triggers are readable, testable, and resource-efficient. Applying the patterns above will help avoid common pitfalls—such as hitting governor limits, causing recursive updates, or creating brittle automation—and ensure your triggers scale as your org grows.








Leave a Reply