Part 55: The Order of Operations in Salesforce
Welcome back to the Salesforce series. We have spent the last several installments building up a deep understanding of Apex — triggers, classes, testing, SOQL, security, and debugging. All of that knowledge converges here. The order of operations (officially called the “order of execution”) is the single most important concept for anyone who writes triggers, builds flows, or troubleshoots automation that behaves in unexpected ways.
Every time a record is saved in Salesforce — whether through the UI, Apex DML, the API, or an automation — the platform runs a fixed sequence of steps. Understanding that sequence is the difference between code that works reliably and code that produces mysterious bugs, fires triggers twice, or silently loses field updates.
This is Part 55 of the series. We will walk through the complete numbered sequence, then work through practical scenarios that show exactly how the order affects real-world development. By the end, you will have a reference you can return to every time something in your org does not behave the way you expected.
Why the Order of Execution Matters
Consider this situation. You write a before trigger that sets a field value on an Account. You also have a validation rule on that same field. You also have a record-triggered Flow that updates a related Contact whenever the Account changes. And you have a roll-up summary field on a parent object that recalculates when the Account is saved.
When a user clicks Save, all of these fire. But in what order? Does your before trigger run before or after the validation rule? Does the Flow see the value your trigger set? Does the roll-up summary recalculate before or after the Flow runs?
If you do not know the answers, you are guessing. And guessing leads to bugs that only surface in production when multiple automations collide.
The order of execution gives you a deterministic map. Learn it once, and you will be able to predict exactly what happens at every stage of a record save.
The Complete Order of Execution
Here is the full sequence that Salesforce follows when a record is saved. This applies to insert, update, delete, and undelete operations, though some steps only apply to certain operations.
Step 1: Load the Original Record
Salesforce loads the original record from the database (for updates and deletes) or initializes a new record (for inserts). The new field values from the save request are loaded into memory and applied to the record.
For updates, this means both Trigger.old and Trigger.new are populated at this stage. For inserts, only Trigger.new exists.
Step 2: System Validation — Required Fields and Field Formats
Before any custom code runs, Salesforce performs its own built-in validation:
- Required fields — Are all required fields populated? If a field is marked as required on the page layout or in the field definition, it must have a value.
- Field format validation — Does the data match the field type? Is an email address in the correct format? Is a number actually a number? Is a date parseable?
- Field length — Does the text value exceed the maximum length for the field?
- Foreign key validation — For lookup fields, does the referenced record exist?
If any of these checks fail, the save is aborted immediately. No triggers fire. No flows run. The user sees an error.
This is important: system validation happens before your code. If a required field is missing, your before trigger never gets a chance to populate it.
Exception: If the save request comes from a standard UI page with specific layout rules, some layout-based validations run here. If it comes from the API or Apex, only field-definition-level validations apply at this stage.
Step 3: Before Triggers Execute
All before triggers on the object fire. If you have multiple before triggers on the same object (which you should avoid — more on that later), their execution order is not guaranteed.
Before triggers are your first opportunity to run custom logic. Because the record has not been saved to the database yet, you can modify field values directly on the trigger records without performing a separate DML statement:
trigger AccountBeforeTrigger on Account (before insert, before update) {
for (Account acc : Trigger.new) {
if (acc.Industry == 'Technology' && acc.Rating == null) {
acc.Rating = 'Hot';
}
}
}
Key points about before triggers in the order of execution:
- The record does not have an ID yet during
before insert. You cannot create child records that reference it. - You can set field values directly on
Trigger.newrecords. This is more efficient than issuing a DML update. - If you call
addError()on a record in a before trigger, the record fails and does not proceed further in the save process. - Before triggers run before custom validation rules. This means your trigger can set or modify values that validation rules will then evaluate.
Step 4: Custom Validation Rules
After before triggers have run, Salesforce evaluates all active custom validation rules on the record. This includes:
- Validation rules defined on the object (Setup > Object > Validation Rules).
- Unique field rules — If a field is marked as unique, the platform checks for duplicates.
- Lookup filter rules — If a lookup field has a filter, the referenced record must pass the filter criteria.
This ordering is critical. Because validation rules run after before triggers, your before trigger can set a field to a value that satisfies a validation rule. Conversely, if your before trigger sets a field to a value that violates a validation rule, the save will fail at this step.
┌──────────────────────────────┐
│ User clicks Save │
└──────────────┬───────────────┘
▼
┌──────────────────────────────┐
│ Step 2: System Validation │
│ (required fields, formats) │
└──────────────┬───────────────┘
▼
┌──────────────────────────────┐
│ Step 3: Before Triggers │◄── You can set fields here
└──────────────┬───────────────┘ to satisfy validation
▼
┌──────────────────────────────┐
│ Step 4: Validation Rules │◄── Evaluates values set
│ (custom, unique, lookup) │ by before triggers
└──────────────┬───────────────┘
▼
continues...
Step 5: Duplicate Rules
If duplicate rules are active on the object, Salesforce evaluates them after validation rules. Duplicate rules use matching rules to identify potential duplicates and can either alert the user or block the save entirely.
Duplicate rules run after before triggers and validation rules, so any field changes made in a before trigger are visible to the duplicate matching logic.
Step 6: Record Saved to Database (Not Yet Committed)
At this point, the record is written to the database but the transaction has not been committed. This is a subtle but important distinction:
- The record now has an ID (for inserts).
- The record is visible to subsequent queries within the same transaction.
- But if anything fails later in the process, the entire transaction rolls back and this save is undone.
Think of it as a provisional save. The data is in the database temporarily, but it is not final until the transaction commits at the very end.
Step 7: After Triggers Execute
All after triggers on the object fire. The record now has an ID, and the values in Trigger.new are read-only.
After triggers are the right place for logic that depends on the record’s ID or that needs to modify other objects:
trigger AccountAfterTrigger on Account (after insert) {
List<Contact> newContacts = new List<Contact>();
for (Account acc : Trigger.new) {
newContacts.add(new Contact(
LastName = 'Default Contact',
AccountId = acc.Id
));
}
insert newContacts;
}
Key points about after triggers:
Trigger.newrecords are read-only. You cannot modify them. If you try, you get a runtime error.- The record has an ID, so you can create child records that reference it.
- DML operations in after triggers on other objects will themselves go through the full order of execution for those objects.
- If you call
addError()on a record in an after trigger, the entire transaction rolls back.
Step 8: Assignment Rules
If assignment rules are active (typically on Leads and Cases), they execute after triggers. Assignment rules evaluate criteria and assign the record to a user or queue.
Assignment rules can change the Owner field on the record. This change does not re-fire triggers by default.
Step 9: Auto-Response Rules
Auto-response rules (used on Leads and Cases) fire after assignment rules. They send automatic email responses based on criteria you define.
Step 10: Workflow Rules (Legacy)
If your org still has active workflow rules, they fire at this point. Workflow rules can perform four actions:
- Field updates (on the same record or a parent record)
- Email alerts
- Outbound messages
- Task creation
Important: If a workflow rule performs a field update on the same record, the record is saved again. This re-save triggers before and after triggers to fire a second time. However, the workflow rules themselves do not re-evaluate on this second pass (preventing an infinite loop).
Workflow rules are legacy functionality. Salesforce recommends migrating them to Flows. But many production orgs still have active workflow rules, so understanding where they sit in the order is essential.
Step 11: Escalation Rules
Escalation rules (used on Cases) execute after workflow rules. They evaluate time-based criteria and can reassign cases or send notifications when conditions are met.
Step 12: Record-Triggered Flows
This is where modern declarative automation lives. Record-triggered Flows execute in a specific sub-order:
- Before-save Flows — These actually run between steps 3 and 4, before validation rules. They are treated similarly to before triggers. You can modify field values on the triggering record without DML.
- After-save Flows — These run at step 12, after workflow rules and escalation rules. They can query and update other records, call sub-flows, and perform complex logic.
Wait — before-save Flows run at a different point than after-save Flows? Yes. This is one of the most confusing aspects of the order of execution. Let me clarify with an updated diagram:
┌──────────────────────────────────┐
│ Step 2: System Validation │
└──────────────┬───────────────────┘
▼
┌──────────────────────────────────┐
│ Step 3: Before Triggers │
└──────────────┬───────────────────┘
▼
┌──────────────────────────────────┐
│ Step 3b: Before-Save Flows │◄── Runs BEFORE validation
└──────────────┬───────────────────┘
▼
┌──────────────────────────────────┐
│ Step 4: Custom Validation Rules │
└──────────────┬───────────────────┘
▼
┌──────────────────────────────────┐
│ Step 5: Duplicate Rules │
└──────────────┬───────────────────┘
▼
┌──────────────────────────────────┐
│ Step 6: Save to DB (not commit) │
└──────────────┬───────────────────┘
▼
┌──────────────────────────────────┐
│ Step 7: After Triggers │
└──────────────┬───────────────────┘
▼
┌──────────────────────────────────┐
│ Step 8: Assignment Rules │
└──────────────┬───────────────────┘
▼
┌──────────────────────────────────┐
│ Step 9: Auto-Response Rules │
└──────────────┬───────────────────┘
▼
┌──────────────────────────────────┐
│ Step 10: Workflow Rules │
│ (if field update → re-trigger) │
└──────────────┬───────────────────┘
▼
┌──────────────────────────────────┐
│ Step 11: Escalation Rules │
└──────────────┬───────────────────┘
▼
┌──────────────────────────────────┐
│ Step 12: After-Save Flows │
└──────────────┬───────────────────┘
▼
continues...
Why before-save Flows run so early: Salesforce designed before-save Flows to be the declarative equivalent of before triggers. They both modify the record in memory before validation and before the database save. This makes them fast (no extra DML) and predictable (they follow the same pattern as before triggers).
Why after-save Flows run so late: After-save Flows need the record to be saved (with an ID) and need to run after triggers and workflow rules so they can react to the final state of the record.
Step 13: Entitlement Rules
Entitlement rules (used in Service Cloud) evaluate whether a case is within the terms of a service entitlement. They run after Flows.
Step 14: Roll-Up Summary Field Calculations
If the saved record is a child in a master-detail relationship, Salesforce recalculates any roll-up summary fields on the parent record. This recalculation can itself trigger the parent record’s order of execution — including the parent’s before triggers, after triggers, and flows.
This is a common source of unexpected cascading behavior.
Step 15: Cross-Object Workflow and Flow Field Updates
If any workflow rule or Flow from step 10 or step 12 performed a field update on a related record (not the triggering record), those updates are executed now. Each cross-object update triggers the full order of execution on the target object.
This is where cascade effects become complex. An update on Account can trigger a Flow that updates a Contact, which triggers the Contact’s triggers, which might update an Opportunity, and so on.
Step 16: Criteria-Based Sharing Rules
Salesforce evaluates criteria-based sharing rules to determine whether the record needs to be shared with additional users or groups based on the record’s field values.
Step 17: DML Transaction Committed to Database
This is the point of no return. Everything that happened in steps 1 through 16 is now permanently committed to the database. If any step had failed, the entire transaction would have rolled back and none of the changes would have persisted.
Once the commit happens, the data is final. You cannot roll it back programmatically.
Step 18: Post-Commit Logic
After the transaction is committed, Salesforce executes post-commit operations:
- Email sends — Emails queued during the transaction (from triggers, workflow rules, or Flows) are sent.
- Asynchronous Apex — Any
@futuremethods, Queueable jobs, or Batch Apex jobs that were enqueued during the transaction are submitted to the async processing queue. - Platform events — Platform events published during the transaction are delivered to subscribers.
- Outbound messages — Outbound messages from workflow rules are sent.
- Change Data Capture events — If Change Data Capture is enabled for the object, events are published.
Post-commit logic cannot roll back the transaction. If an email fails to send or an async job fails to enqueue, the record save is still committed.
The Complete Reference List
Here is the condensed, numbered reference list you can bookmark:
- Load original record from database (or initialize new record)
- Override field values from the save request
- System validation (required fields, field formats, field lengths)
- Before triggers execute
- Before-save record-triggered Flows execute
- Custom validation rules, unique rules, lookup filter rules
- Duplicate rules
- Record saved to database (not committed)
- After triggers execute
- Assignment rules
- Auto-response rules
- Workflow rules execute; if field update on same record, re-save and re-run steps 4-12
- Escalation rules
- After-save record-triggered Flows execute
- Entitlement rules
- Roll-up summary field calculations on parent (triggers parent’s order of execution)
- Cross-object updates from workflows/flows (triggers target object’s order of execution)
- Criteria-based sharing rules evaluated
- DML transaction committed
- Post-commit logic (emails, async Apex, platform events, outbound messages, CDC events)
Order of Execution Examples
Theory is useful, but the real learning happens when you trace through specific scenarios. Let us walk through several practical examples.
Example 1: Before Trigger and Validation Rule Interaction
Scenario: You have an Account with a validation rule that requires the Description field to be non-empty when Industry is “Technology.” You also have a before trigger that auto-populates Description based on Industry.
The validation rule:
AND(
ISPICKVAL(Industry, 'Technology'),
ISBLANK(Description)
)
Error message: “Technology accounts must have a description.”
The before trigger:
trigger AccountBeforeTrigger on Account (before insert, before update) {
for (Account acc : Trigger.new) {
if (acc.Industry == 'Technology' && String.isBlank(acc.Description)) {
acc.Description = 'Technology sector account - pending detailed description.';
}
}
}
What happens when a user creates a Technology Account without a Description?
- System validation passes (Description is not a required field at the system level).
- The before trigger fires and sets
Descriptionto the default text. - The validation rule evaluates. It checks
Industry = Technology(true) andISBLANK(Description)(false, because the trigger set it). The validation passes. - The record saves successfully.
The takeaway: Before triggers run before custom validation rules. Your trigger can set values that make validation rules pass. This is a common and intentional pattern.
What if you used an after trigger instead?
The after trigger runs after the record is saved. But the validation rule runs before the save. So the validation rule would fire first, see a blank Description, and block the save. The after trigger would never execute. This is why field-defaulting logic belongs in before triggers.
Example 2: Before Trigger, After Trigger, and Flow Interaction
Scenario: You have three automations on the Opportunity object:
- A before trigger that sets
StageNameto “Qualification” if it is inserted without a stage. - An after trigger that creates a Task whenever an Opportunity reaches “Closed Won.”
- An after-save Flow that updates the parent Account’s
Descriptionfield whenever any Opportunity on the Account is modified.
What happens when an Opportunity is inserted with StageName = “Closed Won”?
Step 1: System validation passes.
Step 2: Before trigger fires.
- StageName is "Closed Won" (not blank), so the trigger does nothing.
Step 3: Before-save Flows run (if any exist — none in this case).
Step 4: Validation rules evaluate (assume they pass).
Step 5: Duplicate rules evaluate.
Step 6: Record saved to database. Opportunity gets an ID.
Step 7: After trigger fires.
- StageName is "Closed Won" → creates a new Task.
- The Task insert triggers the Task's own order of execution.
Step 8-11: Assignment, auto-response, workflow, escalation rules.
Step 12: After-save Flow fires.
- Updates the parent Account's Description.
- This Account update triggers the Account's order of execution:
- Account before triggers fire
- Account validation rules fire
- Account is saved
- Account after triggers fire
- Account after-save Flows fire
The takeaway: After triggers and after-save Flows that modify other records cause those other records to go through their own complete order of execution. This is how cascading behavior emerges.
Opportunity Insert
│
├─ Before Trigger (set defaults)
├─ Validation Rules
├─ Save to DB
├─ After Trigger
│ └─ Insert Task ──► Task Order of Execution
├─ After-Save Flow
│ └─ Update Account ──► Account Order of Execution
│ ├─ Account Before Triggers
│ ├─ Account Validation
│ ├─ Account Save
│ ├─ Account After Triggers
│ └─ Account Flows
└─ Commit
Example 3: Cross-Object Updates and the Cascade Effect
Scenario: You have the following chain of automations:
- An after trigger on Contact that updates the parent Account’s
Last_Contact_Modified__cfield whenever a Contact is modified. - An after-save Flow on Account that creates a Task for the Account owner whenever
Last_Contact_Modified__cchanges. - A before trigger on Task that sets a default
Prioritybased on the Account’s industry.
What happens when a Contact is updated?
Contact Update
│
├─ Contact Before Triggers
├─ Contact Validation Rules
├─ Contact Save to DB
├─ Contact After Trigger
│ └─ Update Account.Last_Contact_Modified__c
│ │
│ ├─ Account Before Triggers
│ ├─ Account Validation Rules
│ ├─ Account Save to DB
│ ├─ Account After Triggers
│ ├─ Account After-Save Flow
│ │ └─ Insert Task
│ │ │
│ │ ├─ Task Before Trigger (sets Priority)
│ │ ├─ Task Validation Rules
│ │ ├─ Task Save to DB
│ │ ├─ Task After Triggers
│ │ └─ Task Flows
│ └─ Account Commit (nested)
├─ Contact Flows
└─ Contact Commit
A single Contact update cascaded into an Account update which cascaded into a Task insert. Three objects, three full order-of-execution cycles, all within a single transaction.
The danger: Every link in the chain consumes governor limits — SOQL queries, DML statements, CPU time. If any link in the chain fails (a validation rule on the Account, for example), the entire transaction rolls back, including the original Contact update.
Example 4: How Recursion Happens and How to Prevent It
Scenario: You have an after trigger on Account that updates related Contacts. You also have an after trigger on Contact that updates the parent Account. When an Account is updated, the following happens:
Account Update
│
├─ Account After Trigger → Update Contacts
│ │
│ ├─ Contact After Trigger → Update Account ◄── RECURSION
│ │ │
│ │ ├─ Account After Trigger → Update Contacts
│ │ │ │
│ │ │ └─ ... (continues until governor limits hit)
This is infinite recursion. Salesforce will eventually stop it by hitting the governor limit for DML statements or trigger depth, but by then you have consumed enormous resources and the transaction will fail with an error.
How to prevent recursion:
The standard pattern is a static variable that tracks whether the trigger has already run:
public class TriggerHelper {
public static Boolean accountTriggerHasRun = false;
public static Boolean contactTriggerHasRun = false;
}
trigger AccountAfterTrigger on Account (after update) {
if (TriggerHelper.accountTriggerHasRun) {
return;
}
TriggerHelper.accountTriggerHasRun = true;
// Update related Contacts
List<Contact> contactsToUpdate = new List<Contact>();
for (Account acc : Trigger.new) {
// ... build list of contacts to update
}
if (!contactsToUpdate.isEmpty()) {
update contactsToUpdate;
}
}
trigger ContactAfterTrigger on Contact (after update) {
if (TriggerHelper.contactTriggerHasRun) {
return;
}
TriggerHelper.contactTriggerHasRun = true;
// Update parent Accounts
List<Account> accountsToUpdate = new List<Account>();
for (Contact con : Trigger.new) {
// ... build list of accounts to update
}
if (!accountsToUpdate.isEmpty()) {
update accountsToUpdate;
}
}
The static variable persists for the duration of the transaction. The first time the Account trigger runs, it sets the flag. When the Contact trigger causes the Account trigger to fire again, the flag is already true, so the trigger exits immediately.
A more sophisticated approach uses a Set<Id> to track which specific records have been processed, rather than a blanket Boolean:
public class TriggerHelper {
public static Set<Id> processedAccountIds = new Set<Id>();
}
trigger AccountAfterTrigger on Account (after update) {
List<Account> accountsToProcess = new List<Account>();
for (Account acc : Trigger.new) {
if (!TriggerHelper.processedAccountIds.contains(acc.Id)) {
accountsToProcess.add(acc);
TriggerHelper.processedAccountIds.add(acc.Id);
}
}
if (accountsToProcess.isEmpty()) {
return;
}
// Process only unprocessed accounts
}
This approach is more granular. If a bulk operation updates 200 Accounts, and 50 of them are re-triggered by a cascade, only the 50 are skipped — the other 150 would still be processed if they were triggered independently.
Example 5: Why a Flow Field Update Can Re-Fire Triggers
Scenario: You have a before trigger on Case that standardizes the Subject field. You also have an after-save Flow on Case that updates the Status field under certain conditions.
A user creates a Case. Here is what happens:
- Before trigger fires — standardizes the Subject.
- Validation rules pass.
- Case is saved to the database.
- After trigger fires (if any).
- Workflow rules fire (if any).
- After-save Flow fires — updates the Status field on the same Case.
Here is the critical part: when the after-save Flow updates a field on the triggering record, Salesforce performs an internal update on that record. This internal update causes the record to go through the order of execution again — including before triggers and after triggers.
Case Insert (first pass)
├─ Before Trigger (standardize Subject)
├─ Validation Rules
├─ Save
├─ After Trigger
├─ Workflow Rules
├─ After-Save Flow → updates Status field
│ │
│ └─ Case Update (second pass, triggered by Flow)
│ ├─ Before Trigger (runs AGAIN)
│ ├─ Validation Rules (run AGAIN)
│ ├─ Save
│ ├─ After Trigger (runs AGAIN)
│ ├─ Workflow Rules
│ └─ After-Save Flow (does NOT re-fire*)
└─ Commit
* Salesforce prevents after-save Flows from re-firing
on the same record in the same transaction to avoid
infinite loops. But triggers DO re-fire.
The takeaway: If your after-save Flow updates the triggering record, your triggers will run twice. You must design your triggers to handle this. Either use recursion guards or make sure your trigger logic is idempotent (produces the same result regardless of how many times it runs).
Common Pitfalls and How to Avoid Them
Pitfall 1: Assuming Triggers Only Fire Once
As we saw in Example 5, triggers can fire multiple times in a single transaction. Workflow field updates, Flow field updates, and cross-object cascades can all cause re-entry. Always design triggers with the assumption that they may run more than once per transaction.
Pitfall 2: Mixing Before and After Trigger Logic
A before trigger should only modify fields on the triggering record. An after trigger should only perform DML on other records. If you try to update Trigger.new in an after trigger, you get a runtime error. If you perform unnecessary DML in a before trigger (updating the same record you are already in the process of saving), you waste DML statements and can cause recursion.
Pitfall 3: Not Accounting for Before-Save Flow Timing
Before-save Flows run after before triggers but before validation rules. If both your before trigger and your before-save Flow set the same field, the Flow’s value wins because it runs second. This can cause confusion if developers and admins are not communicating about what automations exist on an object.
Best practice: Maintain an automation inventory for each object. Document every trigger, every Flow, and every legacy workflow rule, along with when each one runs in the order of execution.
Pitfall 4: Governor Limits in Cascade Chains
A single transaction shares governor limits across all objects and all levels of the cascade. If your Account trigger uses 80 SOQL queries, and the cascade into Contacts uses 15, and the cascade into Tasks uses 10, you have used 105 of your 100-query limit — and the transaction fails.
When designing automations that cascade across objects, always think about the total governor limit consumption across the entire chain.
Pitfall 5: Relying on Trigger Execution Order
If you have two before triggers on the same object, Salesforce does not guarantee which one runs first. Never write triggers that depend on another trigger having already run. The solution is to use a single trigger per object that delegates to a handler class:
trigger AccountTrigger on Account (
before insert, before update, before delete,
after insert, after update, after delete, after undelete
) {
AccountTriggerHandler handler = new AccountTriggerHandler();
if (Trigger.isBefore) {
if (Trigger.isInsert) handler.beforeInsert(Trigger.new);
if (Trigger.isUpdate) handler.beforeUpdate(Trigger.new, Trigger.oldMap);
if (Trigger.isDelete) handler.beforeDelete(Trigger.old);
}
if (Trigger.isAfter) {
if (Trigger.isInsert) handler.afterInsert(Trigger.new);
if (Trigger.isUpdate) handler.afterUpdate(Trigger.new, Trigger.oldMap);
if (Trigger.isDelete) handler.afterDelete(Trigger.old);
if (Trigger.isUndelete) handler.afterUndelete(Trigger.new);
}
}
With a single trigger and a handler class, you control the execution order of your logic explicitly within the handler.
Pitfall 6: Forgetting Post-Commit Behavior
Emails, async jobs, and platform events are sent after the commit. If your code enqueues a @future method and then the transaction rolls back (because something later in the order of execution fails), the future method is never sent. But if the transaction commits and the future method itself fails, the record is already committed — there is no automatic rollback.
Design your post-commit logic to be resilient to failure. Include error handling in async jobs and consider using platform events with retry mechanisms for critical operations.
Pitfall 7: Workflow Field Updates Causing Double Trigger Execution
This is one of the oldest and most frustrating gotchas in Salesforce. If a legacy workflow rule performs a field update on the same record, the before and after triggers fire again. Many developers have been bitten by this behavior when they add a new trigger to an org that has old workflow rules they did not know about.
If you inherit an org with workflow rules, audit them before writing triggers. Better yet, migrate them to Flows as part of your development work.
A Mental Model for Debugging
When something goes wrong with a record save — an unexpected field value, a validation error that should not fire, a trigger running twice — use this debugging mental model:
- Identify all automations on the object: triggers, before-save Flows, after-save Flows, workflow rules, validation rules, process builders, assignment rules.
- Place each one in the order of execution. Write them down in sequence.
- Trace the data through each step. What is the value of the field at each stage? Which automation modifies it? Which automation reads it?
- Check for cascades. Does any automation update another object? If so, trace that object’s order of execution too.
- Check for re-entry. Does any automation update the same record? If so, trace the second pass through the order of execution.
This methodical approach will solve the vast majority of order-of-execution bugs.
Summary Table: Where Each Automation Type Runs
| Automation Type | When It Runs | Can Modify Triggering Record? |
|---|---|---|
| Before Trigger | Step 4 (early) | Yes, directly on Trigger.new |
| Before-Save Flow | Step 5 (after before triggers) | Yes, via $Record |
| Validation Rule | Step 6 (after before triggers and before-save flows) | No (blocks save on failure) |
| Duplicate Rule | Step 7 | No (blocks save on match) |
| After Trigger | Step 9 (after save) | No (read-only) |
| Assignment Rule | Step 10 | Yes (Owner field) |
| Workflow Rule | Step 12 | Yes (field update, causes re-trigger) |
| Escalation Rule | Step 13 | Yes (reassignment) |
| After-Save Flow | Step 14 | Yes (via DML, causes re-trigger) |
| Roll-Up Summary | Step 16 | Triggers parent recalculation |
Key Takeaways
- The order is deterministic. Salesforce always follows the same sequence. Learn it, and you can predict behavior.
- Before triggers and before-save Flows run before validation rules. Use them to set default values and fix data before validation checks.
- After triggers and after-save Flows run after the record is saved. Use them for cross-object updates and operations that need the record’s ID.
- Field updates from workflow rules and after-save Flows cause re-entry. Triggers fire again on the second pass. Design for this.
- Cascades consume shared governor limits. A single record save can trigger automations on multiple objects, all sharing the same transaction limits.
- Recursion is your responsibility. Use static variables or processed-ID sets to prevent infinite loops.
- One trigger per object. Use handler classes to control execution order within your code.
- Post-commit logic cannot roll back the transaction. Emails, async jobs, and platform events fire after the point of no return.
What Is Next?
In Part 56, we will tackle The Most Common Salesforce Limits — governor limits, API limits, data storage limits, and all the platform boundaries that every Salesforce developer and admin must know. Understanding the order of execution is half the battle; understanding the limits that constrain each step is the other half. See you there.