Salesforce Apex Triggers Best Practices

Salesforce
EmpowerCodes
Oct 29, 2025

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:

trigger AccountTrigger on Account (before insert, before update, after insert, after update) { AccountTriggerHandler.run(Trigger.isBefore, Trigger.isInsert); }

And then:

public with sharing class AccountTriggerHandler { public static void run(Boolean isBefore, Boolean isInsert){ // Your logic here } }

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:

for (Account acc : Trigger.new) { insert new Contact(LastName='Test', AccountId=acc.Id); }

Right way:

List<Contact> contacts = new List<Contact>(); for (Account acc : Trigger.new) { contacts.add(new Contact(LastName='Test', AccountId=acc.Id)); } insert contacts;

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.

if (Trigger.isUpdate) { for (Account acc : Trigger.new) { Account oldAcc = Trigger.oldMap.get(acc.Id); if (acc.Name != oldAcc.Name) { // name changed logic } } }

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.

public class TriggerState { public static Boolean runOnce = true; }

Then check:

if (TriggerState.runOnce) { TriggerState.runOnce = false; // execute logic }

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.

Id recordTypeId = Schema.SObjectType.Account.getRecordTypeInfosByName().get('Business').getRecordTypeId();

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:

try { // logic } catch (Exception e) { System.debug(e.getMessage()); }

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

@future(callout=true) public static void sendDataAsync(String jsonData) { // callout logic }

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.