SfSkills Domains Docs GitHub

Home / admin / workflow-field-update-patterns

workflow-field-update-patterns

Cross-tool decision matrix for field-update automation in Salesforce — Before-Save Flow vs After-Save Flow vs Apex Trigger vs the deprecated Workflow Rule + Field Update.

Open the skill folder on GitHub


Workflow Field Update Patterns

The most common automation question in Salesforce admin work: "I need to set field X to Y when condition Z." The answer depends on whether X is on the same record being saved (cheap, before-save flow), the parent (cross-object after-save), or a child (multi-record DML), plus volume, recursion risk, and whether the value can be derived as a formula instead of stamped.

This skill is the decision layer. It does NOT teach you to build the chosen tool — flow/flow-best-practices, apex/trigger-framework, and so on cover that. It also doesn't redefine the Salesforce order-of-execution sequence — see admin/order-of-execution.

What this skill IS: a decision matrix between the four field-update tools (formula, before-save flow, after-save flow, Apex trigger), plus the migration playbook for replacing legacy Workflow Rule field-update actions (deprecated as of late-2022).


Before Starting


Questions to Ask Before Configuring

Ask these before opening Flow Builder. Each one maps to a specific way this goes wrong later; skipping them produces automation that works on the demo record and diverges from the legacy behaviour it replaced.

AskWhy it mattersWhat a good answer adds
"Which record does the value land on — this one, its parent, its children, or an unrelated one?"Same-record is a free before-save Assignment; anything else is a DML and re-enters the save procedureThe tool, the order-of-execution slot, and whether a recursion guard is needed at all
"Is the target field one the platform computes during save — IsClosed, Amount with line items, ForecastCategory, ActivatedDate?"Before-save cannot write those; the workflow field update at step 11 couldThe choice between before-save and after-save, before the Flow is built and found to do nothing (§ 13)
"Does the existing rule carry reevaluateOnChange, a targetObject, or a workflowTimeTriggers block?"Each maps to something Flow does differently, or not at allThe real scope: one Flow, two Flows, or a cascade to rebuild by hand (§§ 11, 16)
"Which Apex triggers already run on this object, and how many times does each run today?"A field update forces a second update-trigger pass; removing it removes that passA measured before/after trigger-entry count instead of a post-deploy incident (§ 10)
"Does anything read this field later in the same transaction?"Step 3 and step 11 are eight steps apart; readers in between see different valuesThe list of downstream consumers that have to be re-tested, not just the field itself
"Is the source rule onCreateOnly, onCreateOrTriggeringUpdate, or onAllChanges?"It maps to a pair of Flow settings, and getting the pair wrong changes fire frequency without any errorThe correct recordTriggerType plus doesRequireRecordChangedToMeetCriteria, verified by parity rows 4 and 6
"Who owns the field, and what breaks if it stops being written for an hour?"Decides whether cutover can be a single deploy or needs a dual-write windowThe rollback plan, and whether the deploy can go out on a Friday

What a proper configuration adds over just building the Flow: the replacement fires on exactly the saves the old rule fired on, every Apex trigger on the object still runs the number of times its author assumed, and the field-history row after cutover proves which writer produced the value.


Core Concepts

The four field-update tools

ToolTargetGovernor costOrder-of-execution slot
Formula fieldSame record, derivedFree (computed at read time)N/A — not stored
Before-Save FlowSame record, stampedGovernor-free for same-record updatesAfter validation, before save
After-Save FlowSame record (re-DML), parent, child, unrelatedDML governor against limitAfter before-triggers + before-save flows
Apex Trigger (before-update)Same record, stampedSame as before-save flowSame slot (before-save flows are processed alongside)
Apex Trigger (after-update)Parent / child / unrelated via DMLDML governorAfter after-save flows

The standout: before-save flows updating the same record being saved are governor-free. They don't fire a second DML; they modify the in-flight record before commit. This is the cheapest field-update mechanism on the platform.

Same-record before-save vs same-record after-save

Updating a field on the SAME record being saved:

The platform charges you (in DML, governor budget, recursion risk) to do the same work after-save that you can do free before-save. Before-save is the right choice unless you specifically need a value that's only computed after the initial save (record Id on insert is the canonical example — but lookup-relationship integrity often makes after-save the wrong shape too).

Recursion: the after-save trap

A record-triggered after-save flow that updates a field on the same record fires again on its own update. Without a guard, infinite loop — Salesforce's recursion-detection cuts it at around 16 levels with an exception. With a guard (a custom static variable, a field-set check, a "do nothing if X is already set" decision branch in the flow), the second invocation no-ops.

The cleanest guards:

Order of execution overlap with other automation

Salesforce's published order-of-execution is a fixed 20-step sequence. The steps this skill touches, using the documented step numbers:

StepWhat runs
3Before-save record-triggered flows
4Before triggers
5System validation and custom validation rules
6Duplicate rules
7Record saved to the database, not yet committed
8After triggers
9–12Assignment, auto-response, workflow rules (11 — where Workflow Rule field updates fire), escalation
13Process Builder and workflow-launched flows, not in a guaranteed order — the one genuinely indeterminate slot
14After-save record-triggered flows
15Entitlement rules
16–17Roll-up summary fields recalculate on parent, then grandparent
19Commit

Before-save flows (3) and before-update triggers (4) are separate, ordered steps — the flow always runs first. The same holds for after triggers (8) and after-save flows (14): the trigger always runs first, with steps 9–13 in between. Neither pair is interleaved.

Field updates from flows can fire other before-save flows / triggers. Field updates from after-save automation re-enter the loop — this is the recursion source. Plan for it.

Workflow Rule field-update deprecation

Workflow Rules with Field Update actions stopped being creatable in new orgs and stopped accepting new Field Update actions on existing rules in late 2022. They still RUN in orgs that have them; they're not deleted. The migration target is record-triggered flow — typically before-save for same-record stamps, after-save for cross-object updates. The migration tool surfaces them in Setup; the mapping is straightforward.


Common Patterns

Pattern A — Same-record stamp via before-save flow

When to use. Stamp Account.Last_Reviewed_Date__c to TODAY when Account.Status__c = 'Active'. Same record, derived, no parent / child involvement.

Approach. Record-triggered flow on Account, before-save, entry condition Status__c = 'Active' AND ISCHANGED(Status__c). Single Update Records element setting Last_Reviewed_Date__c = {!$Flow.CurrentDate}. Done.

Why before-save. Free (no DML), no recursion possible.

Pattern B — Same-record stamp where formula would be enough

When to use. Opportunity.Display_Stage__c = "Won (" + TEXT(StageName) + ")" for a custom UI rendering.

Wrong instinct. Build a flow that stamps it on every save.

Right answer. Formula field. Computed at read time, no storage, no automation, no recursion, no stale-value risk.

If the formula approach can express it, that's the right answer. Reach for flow / trigger only when the value can't be expressed as a formula (depends on related-record state that formulas can't traverse, requires history that formulas don't have access to, needs to be settable by other automation downstream).

Pattern C — Cross-object update via after-save flow

When to use. When Case.Status__c = 'Closed', decrement the parent Account.Open_Cases__c by 1.

Approach. Record-triggered flow on Case, after-save (must be after-save — cross-object DML can't happen in before-save). Update Records element on the parent Account, decrementing the field.

Recursion guard. The Account update doesn't fire a Case trigger, so no Case-side recursion. But the Account-side automation may react. Account before-save flow that re-stamps a derived field on Account is fine; Account after-save flow that updates Account's own fields is the standard recursion territory.

Pattern D — Apex trigger when flow can't express the logic

When to use. Field update requires complex logic — a Schema describe call, a callout result, a Type.forName dispatch, a batched DML across multiple objects in one transaction.

Approach. Use templates/apex/TriggerHandler.cls as the base. Recursion guard via static Set<Id>. Bulkify; the trigger fires for batches up to 200 (or 2000 for Platform Event triggers).

The honest cost: Apex requires test coverage, deploy via metadata, admin can't edit it. Use only when flow can't express it.

Pattern E — Migrating Workflow Rule field updates

When to use. Existing org with Workflow Rules that include Field Update actions; modernizing to flow.

Per-rule mapping.

Workflow conceptFlow equivalent
Rule trigger "created" / "edited" / "created and any time edited"Record-triggered flow start setting matching the trigger choice
Rule criteriaFlow entry condition
Field Update actionUpdate Records element setting the same field
Re-evaluate workflow rules after field changesDefault flow behavior (chains downstream automation)

Where each operation lands. WorkflowFieldUpdate.operation has six values and they do not all have a Flow equivalent. The full element-by-element mapping — including entry criteria, trigger type and time triggers — is the table in references/metadata-examples.md.

operationReplacement
LiteralAssignment with a typed literal value
Formulaformulas resource + Assignment by elementReference
NullAssignment against an empty value; there is no null operator
LookupValueAssignment of the Id; only User was ever supported here (§ 15)
NextValue / PreviousValueNo equivalent — rebuild as an explicit transition map (§ 12)

Migration order. Use Salesforce's Migrate to Flow tool (Setup → Workflow Rules → Migrate to Flow). It produces a draft flow that you review, test, activate, and only THEN deactivate the original Workflow Rule. Don't deactivate first; the gap leaves the field unstamped. Ship the activation and the deactivation as one deploy — references/metadata-examples.md has the cutover manifest and the field-history verification query.


Decision Guidance

SituationApproachReason
Value is purely derived from other fields on same recordFormula fieldNo automation; no recursion; no governor cost
Stamp same-record field on saveBefore-save flowGovernor-free; no recursion possible
Cross-object update (parent / child / unrelated)After-save flowCross-object DML requires after-save context
Logic is too complex for flow (callouts, Schema describe, etc.)Apex triggerUse TriggerHandler template + recursion guard
Existing Workflow Rule field updateMigrate to record-triggered flowWorkflow Rule field updates are deprecated for new actions
Volume > 100K records on a single save eventApex with explicit bulkificationFlow governor budget can be tight at extreme bulk
Field update fires on every record save indiscriminatelyAdd ISCHANGED() entry conditionOtherwise the automation runs on every save, even no-op
After-save flow updating the same record's other fieldisChanged() guardDefault behavior recurses
Field update produces a value that triggers other automationDocument the chainEach automation level adds governor pressure
Multiple admins each adding their own field-update flowConsolidate into one flow per object"One flow per save event" prevents per-flow overhead and ordering ambiguity

  1. Inventory before designing. Retrieve Workflow with the wildcard manifest in

    references/metadata-examples.md, then run python3 scripts/check_workflow_field_update_patterns.py --manifest-dir force-app/main/default. The inventory block gives the counts that set the scope: field updates per object, reevaluateOnChange flags, targetObject cross-object updates, time triggers, and rules that already defeated the Migrate to Flow tool.

  2. Rule the field out of automation entirely. If the value is a function of same-record

    fields at read time, it is a formula field — references/examples.md Example 4 has the CustomField XML and the one behavioural difference (formulaTreatBlanksAs) to check before deleting the automation it replaces.

  3. Answer the seven questions above and route. automation-selection.md Q2 (same record,

    under ~10s) → before-save Flow; Q4/Q5 (crosses objects, linear) → after-save Flow; Q3 (callout retry, savepoints, recursive same-object DML) → Apex on templates/apex/TriggerHandler.cls. flow-pattern-selector.md Q3 confirms before-save vs after-save; Q5 routes a time trigger to a scheduled path.

  4. Build from the matching example, not from scratch.

    references/metadata-examples.md Example 2 (before-save Flow), Example 3 (after-save, targetObject case), Example 4 (Apex beforeUpdate excerpt). Map every legacy element through the table there; anything with no row is a design decision to record, not to skip.

  5. Fill templates/workflow-field-update-patterns-template.md. Sections 3 and 4 are the

    ones that catch migrations: the element-by-element legacy mapping, and the measured trigger-entry count per save before and after the cutover.

  6. Run the checker against the change, not just the org. Re-run

    check_workflow_field_update_patterns.py --manifest-dir <tree> on the branch. Zero ERRORs is the deploy gate; the double-writer WARN firing means the cutover is only half-built.

  7. Parity-test, then cut over in one deploy. Run the seven-row table from

    references/metadata-examples.md with the legacy rule live, then with it deactivated — identical results both times. Deploy the Active Flow and <active>false</active> together, then confirm with the field-history query.


Review Checklist


Salesforce-Specific Gotchas

  1. Before-save flows are governor-free for same-record updates. Don't re-implement same-record stamps in after-save. (See references/gotchas.md § 1.)
  2. After-save flow updating the same record recurses without a guard. Default behavior; explicit guard required. (See references/gotchas.md § 2.)
  3. Workflow Rule field updates are deprecated for new actions as of late 2022 — migrate or accept they're frozen. (See references/gotchas.md § 3.)
  4. Formula fields are computed at read time, not stored. Reports / dashboards can sort / filter on them but at query cost. (See references/gotchas.md § 4.)
  5. Before-save flows run at step 3, before-update triggers at step 4 — the order is fixed and documented, so a before trigger always sees values the before-save flow already wrote, and can overwrite them. (See references/gotchas.md § 5.)
  6. Cross-object update from a flow fires the target object's automation. Plan the chain. (See references/gotchas.md § 6.)
  7. Multiple flows on the same object firing on the same save event all run; ordering is not guaranteed across flows. (See references/gotchas.md § 7.)
  8. ISCHANGED() is true on insert — the value changed from null. Scope the trigger setting deliberately. (See references/gotchas.md § 8.)
  9. Migrate to Flow produces a draft and leaves the source rule active. Both writers run until you deactivate it. (See references/gotchas.md § 9.)
  10. Retiring a field update deletes a second update-trigger pass that no Apex author wrote down; trigger behaviour changes in a deploy containing no Apex. (See references/gotchas.md § 10.)
  11. reevaluateOnChange restarts every rule on the object, up to five cascades. Flow has no equivalent element. (See references/gotchas.md § 11.)
  12. NextValue / PreviousValue walk the picklist's own order and have no Flow counterpart; rebuild as a transition map. (See references/gotchas.md § 12.)
  13. A before-save Flow cannot write the fields the system computes during save — the workflow field update at step 11 could. (See references/gotchas.md § 13.)
  14. In the pass after a field update, Trigger.old holds pre-update values, not what the user submitted. (See references/gotchas.md § 14.)
  15. LookupValue only ever supported User, despite lookupValueType advertising Queue and RecordType. (See references/gotchas.md § 15.)
  16. A rule with both immediate and time-dependent field updates becomes two Flows, in different save-time slots. (See references/gotchas.md § 16.)

Output Artifacts

ArtifactDescription
Field-update tool decisionFormula / before-save flow / after-save flow / trigger, with rationale
Recursion-guard implementationISCHANGED / static set / decision-branch — explicit and tested
Entry condition expressionScope the automation to relevant changes
Workflow Rule migration planIf applicable: Migrate-to-Flow output, sandbox test, deactivation gating

Reference Files

FileRead it when
references/metadata-examples.mdWriting the deployable XML: the legacy Workflow file, the before-save and after-save Flow replacements, the Apex variant, the element-by-element mapping table, package.xml, retrieve/deploy commands, the cutover sequence and the parity test table
references/gotchas.mdSixteen platform behaviours that make a correct-looking field update wrong — the second trigger pass, reevaluateOnChange, NextValue, before-trigger write restrictions, Trigger.old, LookupValue, time triggers
references/examples.mdLooking for a worked case: after-save done wrong, cross-object counter, a WFR migration with the inventory commands, the formula-field replacement with its CustomField XML, the Apex-only case
references/llm-anti-patterns.mdReviewing generated field-update advice — the eight failure modes assistants reproduce from pre-2022 training data
references/well-architected.mdFraming the choice against Reliability and Operational Excellence, and for the full source list with line references
templates/workflow-field-update-patterns-template.mdBefore building or migrating any field update worth reviewing — the decision record, the legacy mapping table, and the cutover checklist
scripts/check_workflow_field_update_patterns.pyInventorying an org, and as the pre-deploy gate on a migration branch (--manifest-dir)

Package files