Study guide

8.1 Trigger Structure & Context Variables

Salesforce Certified Platform Developer I Exam

Track your progress as you read

Finished sections get a check mark.

1 Introduction & Exam Overview

1.1 Current Exam Facts 1.2 Blueprint Weights & Study Strategy

2 Platform Fundamentals

2.1 Multi-Tenant Architecture & Shared Resources 2.2 MVC Mapping on the Lightning Platform 2.3 Declarative vs Programmatic Decisions 2.4 Agentforce for Developers: Use Cases & Limitations

3 Data Modeling & Schema

3.1 Objects, Fields & Schema Design 3.2 Relationship Types & Implications 3.3 Schema Design Impact on Apex 3.4 Data Import & Export for Development

4 Platform Data Features

4.1 Formula Fields & Roll-Up Summaries 4.2 Core CRM Objects & Standard Schema 4.3 AppExchange Extensions & Platform Boundaries

5 Declarative Process Automation

5.1 Record-Triggered Flows & Declarative Automation 5.2 Approval Processes & Business Rules 5.3 When to Use Apex vs Declarative Tools

6 Apex Language Basics

6.1 Variables, Constants & Apex Data Types 6.2 Lists, Sets & Maps 6.3 Control Flow & Exception Handling 6.4 Classes, Interfaces & OOP Patterns

7 SOQL, SOSL & DML

7.1 SOQL Query Fundamentals 7.2 SOSL Search Fundamentals 7.3 DML Operations & Bulkification 7.4 Governor Limits & Transaction Boundaries

8 Apex Triggers & Order of Execution

8.1 Trigger Structure & Context Variables 8.2 Bulk Trigger Patterns & Best Practices 8.3 Save Order of Execution 8.4 Recursion, Cascading & Transaction Control

9 Asynchronous Apex

9.1 @future Methods 9.2 Queueable Apex 9.3 Batch Apex 9.4 Schedulable Apex & Job Orchestration

10 Security, Events & Schema Access

10.1 Sharing, FLS, CRUD & Injection Prevention 10.2 Platform Events Publish & Subscribe 10.3 Programmatic Schema Access (Describe)

11 Visualforce

11.1 Visualforce Pages & Markup 11.2 Standard, Custom Controllers & Extensions 11.3 Visualforce in Lightning Experience

12 Lightning Components

12.1 Lightning Component Framework Benefits 12.2 Aura Component Structure & Resources 12.3 Lightning Web Components Fundamentals 12.4 LWC Data Access: Wire, Imperative & Events

13 UI Composition & Apex Integration Patterns

13.1 Composing UI in Lightning Apps 13.2 UI Security, Performance & Accessibility Patterns 13.3 Apex Actions for Flow, LWC, and Agentforce

14 Apex Testing

14.1 Testing Framework & Coverage Requirements 14.2 Writing Unit Tests for Triggers & Classes 14.3 Test Data Strategies & Isolation 14.4 Executing Tests & Execute Anonymous Differences

15 Debugging & Developer Tools

15.1 Debug Logs & Monitoring 15.2 Developer Console, CLI, VS Code & Workbench 15.3 Sandboxes, Scratch Orgs & Environment Strategy

16 Deployment & Release

16.1 Change Sets & Metadata Deployment 16.2 Unlocked Packages, Metadata API & Salesforce DX 16.3 Release Readiness & Credential Maintenance

8.1 Trigger Structure & Context Variables

Key Takeaways Explain This Topic AI

Apex triggers fire on sObject DML events: before/after insert, update, delete, and undelete (before undelete is not supported)

Trigger.new holds new versions of records; Trigger.old holds prior versions on update/delete; Map variants index by Id for bulk-safe lookups

Context booleans (Trigger.isInsert, isUpdate, isDelete, isUndelete, isBefore, isAfter) route logic without guessing the event

Use before triggers to validate and modify fields on the triggering records without extra DML; use after triggers for related-record DML and work that needs permanent Ids

Platform best practice is one trigger per object that delegates to a handler class—never scatter business rules across multiple unmanaged triggers

Last updated: August 2026

8.1 Trigger Structure & Context Variables

Quick Answer: An Apex trigger runs automatically when records of a chosen sObject are inserted, updated, deleted, or undeleted. Use before context to validate and change fields on the triggering records without extra DML; use after context for related DML and anything that needs committed Ids. Master Trigger.new , Trigger.old , their Map forms, and the isInsert / isUpdate / isBefore flags so you route bulk-safe logic correctly on Platform Developer I.

Triggers are the classic Apex entry point for record automation. Even when Flow is preferred for simple declarative work, the exam expects you to read trigger syntax, choose before vs after correctly, and use context variables under bulk data loads—not single-record assumptions.

  • Trigger Declaration Syntax
  • A trigger is bound to one sObject and one or more events: trigger AccountTrigger on Account (
  • before insert,
  • before update,
  • after insert,
  • after update,
  • before delete,
  • after delete,
  • after undelete
  • ) {
  • // route by context, then call a handler (see 8.2)
  • }
  • Supported events
  • Event When it fires Notes
  • before insert Before new rows are saved No Id yet on brand-new records
  • after insert After rows are saved (not yet fully committed in the broader transaction model) Ids available
  • before update Before existing rows are saved Both new and old versions available
  • after update After existing rows are saved Both new and old versions available
  • before delete Before rows are removed Use Trigger.old (no Trigger.new)
  • after delete After rows are removed Use Trigger.old
  • after undelete After restore from Recycle Bin Restored records in Trigger.new

There is no before undelete . Merging, cascade deletes, and undelete have nuanced context availability—read the event table carefully on scenario questions.

Trigger names must be unique in the org and typically end with Trigger (for example ContactTrigger ). The body is not a class: you cannot declare methods inside a trigger. Put reusable logic in Apex classes and call them from the trigger.

Context Variables You Must Know

Salesforce populates static context on the Trigger class for the duration of the invocation:

Trigger.new and Trigger.old

Trigger.new — List<sObject> of the new versions of the records. Present on insert, update, and undelete. On before insert/update , you may change field values on these sObjects and those changes participate in the save without a separate update DML.

Trigger.old — List<sObject> of the prior versions. Present on update and delete. Read-only. Compare old vs new to detect field changes. for (Account a : Trigger.new) {

  • Account prior = Trigger.oldMap.get(a.Id); // update only
  • if (prior != null && a.Name != prior.Name) {
  • // name changed
  • }
  • }

On delete , iterate Trigger.old (or oldMap). Trigger.new is not available in delete context.

Trigger.newMap and Trigger.oldMap

Trigger.newMap — Map<Id, sObject> of new records by Id. Available when records already have Ids (update, after insert, undelete paths as documented). Not available in before insert in the usual sense for brand-new rows without Ids.

Trigger.oldMap — Map<Id, sObject> of prior versions by Id on update/delete.

Maps are the bulk-safe way to fetch the counterpart of a record without scanning lists repeatedly: for (Contact c : Trigger.new) {

  • Contact oldC = Trigger.oldMap.get(c.Id);
  • if (oldC.Email != c.Email) {
  • // email changed—collect for later work
  • }
  • }
  • Operation and timing flags
  • Flag True when
  • Trigger.isBefore / Trigger.isAfter Timing relative to the save of those records
  • Trigger.isInsert Insert operation
  • Trigger.isUpdate Update operation
  • Trigger.isDelete Delete operation
  • Trigger.isUndelete Undelete operation
  • Trigger.isExecuting Code is running in trigger context
  • Trigger.size Number of records in this invocation (1–200 in a bulk chunk)
  • Trigger.operationType System.TriggerOperation enum (INSERT, UPDATE, DELETE, UNDELETE with BEFORE/AFTER)

Modern handlers often switch on Trigger.operationType instead of stacking many booleans: switch on Trigger.operationType {

  • when BEFORE_INSERT {
  • AccountTriggerHandler.beforeInsert(Trigger.new);
  • }
  • when BEFORE_UPDATE {
  • AccountTriggerHandler.beforeUpdate(Trigger.new, Trigger.oldMap);
  • }
  • when AFTER_INSERT {
  • AccountTriggerHandler.afterInsert(Trigger.new);
  • }
  • when AFTER_UPDATE {
  • AccountTriggerHandler.afterUpdate(Trigger.new, Trigger.oldMap);
  • }
  • when BEFORE_DELETE {
  • AccountTriggerHandler.beforeDelete(Trigger.old);
  • }
  • when AFTER_DELETE {
  • AccountTriggerHandler.afterDelete(Trigger.old);
  • }
  • when AFTER_UNDELETE {
  • AccountTriggerHandler.afterUndelete(Trigger.new);
  • }
  • }
  • Before vs After: Choosing the Right Context
  • Before triggers — validate and stamp the same records

Use before when you need to:

  • Default or normalize field values ( Status__c = 'New' , trim strings, stamp Last_Synced__c )
  • Block saves with addError on a record or field
  • Enforce cross-field rules that should fail the DML before it persists

Avoid an extra DML on the same records (changing Trigger.new in before update/insert is free relative to after-update that updates the same rows again) // before insert or before update

  • for (Opportunity o : Trigger.new) {
  • if (o.Amount != null && o.Amount < 0) {
  • o.Amount.addError('Amount cannot be negative');
  • }
  • if (o.CloseDate == null) {
  • o.CloseDate = Date.today().addDays(30);
  • }
  • }

addError on a record or field marks that row (or the whole operation depending on allOrNone behavior) as failed. Prefer field-level messages for UI clarity.

Before insert caveat: records do not yet have Salesforce Ids. You cannot put new records into a Map by Id, create children that need ParentId, or query “this” record by Id. Related DML that needs the new Id belongs in after insert .

After triggers — related work and Id-dependent logic

Use after when you need to:

  • Insert/update/delete related records (Tasks, child custom objects, junction rows)
  • Callout-prep or async work that should only run after a successful save path for these records
  • Read values that platform logic may have set during the save
  • Work with Ids of newly inserted parents // after insert
  • List<Task> tasks = new List<Task>();
  • for (Case c : Trigger.new) {
  • tasks.add(new Task(
  • WhatId = c.Id,
  • Subject = 'Follow up new Case',
  • Status = 'Not Started',
  • Priority = 'Normal'
  • ));
  • }
  • if (!tasks.isEmpty()) {
  • insert tasks;
  • }

Do not modify Trigger.new field values in after context expecting them to persist on the same save—those records are already past the before-save modification window. Changing fields in after usually requires a separate update , which re-enters the order of execution and can cause recursion (covered in 8.3 and 8.4).

One Trigger per Object (Best Practice Overview)

Multiple triggers on the same object and event are allowed, but execution order among peer triggers is not guaranteed . That creates race conditions, double-processing risk, and untestable ordering assumptions.

  • Recommended shape
  • One trigger per sObject listing all needed events
  • Trigger body only routes context → static methods on a handler class
  • Handler orchestrates; service classes hold domain logic and queries
  • Optional framework (trigger handler base class) for recursion flags and bypass

This pattern is the default “best practice” answer on exam design questions. Fat triggers full of SOQL, nested loops, and duplicated rules are anti-patterns even if they “work” for one record in the Developer Console.

Bulk Context Is Always On

Triggers always receive lists , even when a user saves one record. Platform bulk APIs can deliver up to 200 records per chunk. Code that assumes Trigger.new[0] only, or runs SOQL/DML once per iteration without collecting Ids, fails governors under load. Section 8.2 deepens bulk patterns; for this section, treat every loop over Trigger.new/old as a bulk set.

  • Exam Checklist for Context Variables
  • Insert before: new yes, old no, Ids not yet assigned
  • Update: new and old (and maps) yes—compare for change detection
  • Delete: old yes, new no
  • Undelete: after only, new yes
  • Field edits without extra DML → before
  • Related DML needing parent Id → after insert
  • Prefer Trigger.operationType or clear isBefore/isInsert routing over guessing
  • One trigger per object + handler class for maintainability questions

Master structure and context first; bulk frameworks, order of execution, and recursion build directly on these rules.