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.