Triggers
A trigger is Apex that runs automatically before or after DML events on a specific object.
18.1 What is a trigger?
A trigger is Apex that runs automatically before or after DML events on a specific object. Triggers handle records in bulk — up to 200 at a time per chunk — whether the change came from the UI, API, Data Loader, Flow, or other Apex.
trigger AccountTrigger on Account (
before insert, before update, before delete,
after insert, after update, after delete, after undelete
) {
AccountTriggerHandler.handle(); // keep logic out of the trigger body (§19)
}18.2 Before vs after
| | Before triggers | After triggers | |-|-----------------|----------------| | Use for | Validating or modifying the records being saved | Working with related records, using record IDs, async work | | Modify Trigger.new fields? | Yes — directly, no DML needed | No (read-only; DML on them raises an error) | | Record Id available? | Not on insert | Yes | | System fields (CreatedDate, etc.) | Not on insert | Yes |
18.3 Context variables
New versions (insert, update, undelete) Old versions (update, delete) Map<Id, SObject>
New versions by ID (before update, after insert/update/undelete) Old versions by ID (update, delete)
Trigger.isInsert / isUpdate / isDelete / isUndelete Trigger.isBefore / isAfter True when running in a trigger context BEFORE_INSERT, AFTER_UPDATE, … Number of records in this invocation Availability by event:
18.4 A complete (simple) trigger
trigger OpportunityTrigger on Opportunity (before insert, before update, after update) {
if (Trigger.isBefore) {
for (Opportunity opp : Trigger.new) {
// default values and validation
if (opp.Amount == null) opp.Amount = 0;
if (opp.Amount < 0) {
opp.Amount.addError('Amount cannot be negative.'); // field-level error
}
}
}
if (Trigger.isAfter && Trigger.isUpdate) {
// create a follow-up task when an opportunity becomes Closed Won
List<Task> tasks = new List<Task>();
for (Opportunity opp : Trigger.new) {
Opportunity oldOpp = Trigger.oldMap.get(opp.Id);
if (opp.StageName == 'Closed Won' && oldOpp.StageName != 'Closed Won') {
tasks.add(new Task(
WhatId = opp.Id, OwnerId = opp.OwnerId,
Subject = 'Send welcome kit', ActivityDate = Date.today().addDays(3)
));
}
}
if (!tasks.isEmpty()) insert tasks; // one DML for all records
}
}18.5 Detecting field changes
for (Account acc : (List<Account>) Trigger.new) {
Account oldAcc = (Account) Trigger.oldMap.get(acc.Id);
if (acc.OwnerId != oldAcc.OwnerId) { /* owner changed */ }
}18.6 Trigger rules of thumb
One trigger per object: multiple triggers on the same object run in an undefined order.
No logic in the trigger body: delegate to a handler class (testable, reusable, controllable).
Always bulkify: no SOQL or DML inside loops (§21).
Use addError() to block saves with user-friendly messages; it also rolls back that record.
Triggers fire on platform events (after insert only) and Change Data Capture events (§25).
Some operations don't fire triggers: cascade deletes of child records (in some cases), mass updates of picklist values, certain bulk updates such as changing record ownership via mass transfer (check documentation), and truncating custom objects.