Trigger.new vs Trigger.old — Understanding Apex Triggers the Right Way
Source: Dev.to
Introduction
When learning Apex triggers, the key is understanding when to use Trigger.new versus Trigger.old.
- Trigger.new – the data Salesforce is trying to save right now.
- Trigger.old – the data that existed before the change.
Keeping this distinction in mind makes most usage scenarios logical.
Trigger.new
What it represents
Trigger.new contains the current or incoming values that Salesforce is attempting to insert or update.
Common use cases
- Validations
- Setting or modifying field values
- Reading updated values
- Creating related records based on new data
Available in these contexts
| Scenario | Available? |
|---|---|
| Insert | ✅ |
| Update | ✅ |
| Undelete | ✅ |
Undelete: This trigger context runs when a record is restored from the Recycle Bin, and Trigger.new contains the record values being recovered.
Example: Validation
for (Contact con : Trigger.new) {
if (con.Email == null) {
con.Email.addError('Email is required');
}
}
The validation works because Trigger.new represents the data being saved at that moment.
Trigger.old
What it represents
Trigger.old is a snapshot of the record before the change.
Common use cases
- Detecting field changes
- Conditional logic based on previous values
- Preventing duplicate actions
Available in these contexts
| Scenario | Available? |
|---|---|
| Update | ✅ |
| Delete | ✅ |
Example: Detecting a change
for (Opportunity newOpp : Trigger.new) {
Opportunity oldOpp = Trigger.oldMap.get(newOpp.Id);
if (newOpp.StageName != oldOpp.StageName) {
// Stage has changed
}
}
Trigger.old provides the previous state for comparison.
Example: Detecting a meaningful update
for (Opportunity newOpp : Trigger.new) {
Opportunity oldOpp = Trigger.oldMap.get(newOpp.Id);
if (newOpp.StageName == 'Closed Won' &&
oldOpp.StageName != 'Closed Won') {
// Stage just changed to Closed Won
}
}
Tips
- Trigger.oldMap and Trigger.newMap give fast access to records by Id (
Id → Record). - Using maps is preferred over looping when comparing old and new values in bulk operations.
- Whenever your logic depends on detecting a change (not just the current value), you need both
Trigger.newandTrigger.old.
Context‑Based Decision Table
| Requirement | Use |
|---|---|
| Validate input | Trigger.new |
| Modify fields | Trigger.new |
| Compare old vs new | Trigger.new + Trigger.old |
| Cleanup before delete | Trigger.old |
| Block record save | Trigger.new.addError() |
Trigger Types
| Trigger Type | What to Do |
|---|---|
| Before | Modify Trigger.new |
| After | Read Trigger.new, compare with Trigger.old |
Important: You cannot modify records using
Trigger.newin after triggers because the record has already been committed to the database. At this stage,Trigger.newis read‑only. Any further changes require a separate DML operation.
Example: Updating in an after trigger using DML
List<Account> accsToUpdate = new List<Account>();
for (Account acc : Trigger.new) {
accsToUpdate.add(new Account(
Id = acc.Id,
Name = 'Updated Name'
));
}
update accsToUpdate;
Trigger Context Overview
| Context | Available Data | Purpose |
|---|---|---|
| Before Insert | Trigger.new | Record does not exist yet; modify fields before save |
| Before Update | Trigger.new + Trigger.old | Compare old vs new; modify fields before save |
| After Update | Trigger.new + Trigger.old | Record saved; read‑only, detect changes |
| Before Delete | Trigger.old | Record about to be deleted; cleanup logic |
| After Delete | Trigger.old | Record deleted; read‑only, audit or related logic |
Visualizing the timeline of data makes choosing the correct context natural. Instead of memorizing rules, ask:
“Am I working with new data, old data, or a change between them?”
Once answered, the appropriate context (Trigger.new, Trigger.old, or both) becomes obvious.
Best Practices
- Bulkify your triggers – loop over collections like
Trigger.newinstead of single records to avoid governor limits. - Use
Trigger.newMapandTrigger.oldMapfor efficient lookups by Id instead of nested loops.