Apex triggers are powerful server-side blocks of code that execute before or after database events occur on Salesforce objects. Understanding trigger execution events, context variables (Trigger.new, Trigger.oldMap), and architectural standards like the "One Trigger Per Object" rule ensures your code runs efficiently and stays well within platform governor limits.
1. Trigger Syntax and Structure
An Apex trigger is defined on a specific sObject and listens for one or more database event keywords:
trigger AccountTrigger on Account (before insert, before update, after insert, after update) {
// Trigger routing and logic delegation happens here
}
2. Supported Trigger Events
before insert: Fires before records are saved to the database. Ideal for populating default field values or running validation checks.before update: Fires before existing record updates are committed. Used to validate changes against previous field states.before delete: Fires before records are deleted. Used to enforce deletion restrictions.after insert: Fires after records are saved. Ideal for creating related child records or publishing platform events.after update: Fires after updates complete. Used for cross-object rollups or asynchronous synchronization.after delete: Fires after records are deleted from the database.after undelete: Fires when records are restored from the Recycle Bin.
Trigger.new: List of new sObject versions (available in insert, update, undelete).Trigger.old: List of old sObject versions prior to updates or deletions (available in update, delete).Trigger.newMap: Map of IDs to new sObject versions (available in insert, update, undelete).Trigger.oldMap: Map of IDs to old sObject versions (available in update, delete).Trigger.isExecuting: Returns true if the current code is executing via a trigger context.
3. Common Trigger Code Patterns
Iterating over
Trigger.new to set default values before records hit the database.
trigger ContactTrigger on Contact (before insert) {
for (Contact con : Trigger.new) {
if (String.isBlank(con.LeadSource)) {
con.LeadSource = 'Web';
}
}
}
Comparing old and new field values using
Trigger.oldMap.
trigger CaseTrigger on Case (after update) {
List<Case> escalatedCases = new List<Case>();
for (Case newCase : Trigger.new) {
Case oldCase = Trigger.oldMap.get(newCase.Id);
// Check if status changed to Escalated
if (newCase.Status == 'Escalated' && oldCase.Status != 'Escalated') {
escalatedCases.add(newCase);
}
}
if (!escalatedCases.isEmpty()) {
CaseNotificationService.sendEscalationAlerts(escalatedCases);
}
}
4. Common Traps & Best Practices
Placing SOQL queries or DML statements inside
for (Contact c : Trigger.new) loops will instantly exhaust governor limits when processing bulk data imports (e.g., 200 records). Always query data in bulk collections outside loops and process records in lists.
- Adopt a Trigger Framework: Avoid writing raw business logic inside trigger files. Route events through an extensible handler class to guarantee execution order and maintainability.
- Enforce User Mode Security: Append
WITH USER_MODEon SOQL queries and useas useron DML statements to respect object and field-level permissions automatically. - Write Comprehensive Unit Tests: Ensure your test classes cover bulk data operations (200 records) and assert expected outcomes explicitly using modern
Assert.areEqual()syntax.
Summary
Mastering Apex triggers is a core requirement for any Salesforce developer. By utilizing context variables correctly, keeping trigger files lightweight, bulkifying database operations, and adopting structured handler frameworks, you can build high-performance automation workflows that scale cleanly across enterprise organizations.