Salesforce Apex Triggers Best Practices
Salesforce Apex triggers are a powerful feature used to perform custom actions before or after data manipulation events such as insert, update, delete, or undelete operations. While triggers offer tremendous flexibility, improper implementation can cause serious performance bottlenecks, unexpected recursion, governor limit violations, and difficult-to-maintain code. That’s why following industry-standard best practices is essential.
In this blog, we’ll explore the top Salesforce Apex trigger best practices with examples, patterns, and recommendations to ensure clean, scalable, and enterprise-grade implementations.
1. Always Use One Trigger per Object
One of the most fundamental rules in Salesforce development is to use only one trigger per object. Multiple triggers on the same object can lead to unpredictable execution order, making the logic harder to manage.
Bad Practice: Multiple triggers on Account
-
Trigger A updates fields
-
Trigger B performs validation
-
Trigger C inserts related records
Because Salesforce does not guarantee execution order, triggers may run in unexpected sequences.
Good Practice:
Combine all logic into a single trigger and delegate to handler classes.
2. Keep Logic Out of Triggers (Use a Handler Class)
Triggers should contain minimal code. The best pattern is to forward requests to a dedicated Apex class (Trigger Handler). This approach improves readability, reusability, and testability.
Example structure:
And then:
This separation of concerns is critical for long-term maintenance.
3. Bulkify All Trigger Logic
Triggers must support bulk operations. Salesforce can process up to 200 records in a single transaction, such as:
-
Mass update through Data Loader
-
Bulk API calls
-
Mass assignment rules
Avoid using queries, DML, or complex operations inside loops.
Wrong way:
Right way:
This prevents resource exhaustion and governor limit failures.
4. Avoid SOQL and DML Inside Loops
Salesforce enforces strict governor limits on:
-
SOQL queries (100 max)
-
DML statements (150 max)
Running these inside loops is one of the most common causes of deployment failures.
Instead, gather data first, store in collections, and execute queries or DML once.
5. Use Collections (Maps & Sets) Effectively
Maps and Sets improve performance dramatically.
Use a Set to gather unique IDs and query related records once. Use Maps to reference them efficiently.
Example use case:
-
Set<Id> accIds = new Set<Id>();
-
Map<Id, Account> accMap = new Map<Id, Account>();
Collections optimize query logic and reduce redundancy.
6. Use Trigger Context Variables Properly
Salesforce triggers offer context variables that provide valuable information:
-
Trigger.new -
Trigger.old -
Trigger.isInsert -
Trigger.isUpdate -
Trigger.isDelete
… and more.
Example: Validate a field only when updated.
Incorrect use of these variables may trigger null pointer exceptions or incorrect logic.
7. Use Recursion Prevention Techniques
Triggers can mistakenly invoke themselves repeatedly.
Common recursion scenario:
-
Trigger updates a field
-
That update fires the trigger again
-
Infinite loop occurs
Solution:
Use static variables in a helper class.
Then check:
This prevents re-execution within the same transaction.
8. Handle Before vs After Triggers Correctly
Before triggers
-
Modify the record before saving
-
Ideal for validation and default values
After triggers
-
Good for tasks that rely on record IDs (like inserting related records)
Misusing these contexts can lead to DML errors.
9. Write Comprehensive Test Classes
Salesforce requires at least 75% code coverage for deployment, but best practice is aiming for 90%+.
Test classes must:
-
Test both positive and negative scenarios
-
Include bulk operations
-
Validate behavior
Example checklist:
-
Trigger.new and Trigger.old logic
-
Single record execution
-
Bulk update
-
Recursion prevention
-
Field updates
Include Test.startTest() and Test.stopTest() for accurate performance measurement.
10. Avoid Hardcoding IDs
Never hardcode:
-
Record types
-
Profiles
-
Picklist values
Instead, query them dynamically.
This ensures code works across environments.
11. Use Custom Metadata and Custom Settings
To avoid deploying code for configuration changes, use:
-
Hierarchy settings
-
Custom metadata types
This allows modifying behavior without code changes.
Example:
-
Enable/disable trigger features
-
Define threshold values
-
Set validation rules dynamically
12. Handle Exceptions Gracefully
Never expose Salesforce exceptions directly to users.
Use:
or store errors in custom logs.
Meaningful exception handling improves debugging and system stability.
13. Future Methods & Queueable Apex for Callouts
If your trigger needs to call external systems:
-
Triggers cannot directly perform callouts
-
Use asynchronous Apex
Queues are ideal for complex jobs.
14. Implement Trigger Frameworks
As systems grow, triggers become complex. Enterprise Salesforce implementations commonly adopt frameworks such as:
-
TDX Trigger Framework
-
Apex Enterprise Patterns
-
Trigger Action Framework
Benefits:
-
Consistent structure
-
LBV (logic by behavior)
-
Improved unit testing
15. Document Your Code
Clear documentation prevents future confusion.
Include:
-
Purpose
-
Preconditions
-
Postconditions
-
Known limitations
Future developers will thank you!
Final Thoughts
Apex triggers are indispensable in automation, but without proper design patterns, they can quickly become unmanageable. By following the best practices discussed above, developers can create triggers that are:
-
Scalable
-
Maintainable
-
Efficient
-
Testable
-
Limit-friendly
Remember:
-
Keep logic out of triggers
-
Bulkify everything
-
Avoid hardcoding
-
Use handler classes
-
Write strong test coverage
Adopting these practices not only improves code quality but also ensures your Salesforce org remains healthy and future-proof.