AgentSkillsCN

Salesforce Flows

Salesforce Flow Builder平台参考——类型、元素、时机、故障处理,以及Flow与Apex之间的决策指导。

SKILL.md
--- frontmatter
name: Salesforce Flows
description: Platform reference for Salesforce Flow Builder — types, elements, timing, fault handling, and decision guidance for Flow vs Apex.
when_to_use: When building, debugging, or advising on Salesforce Flows. Use for element selection, trigger timing decisions, fault handling patterns, and determining whether logic belongs in Flow or Apex.
version: "62.0"

Salesforce Flows — Platform Reference (API v62.0)

Flow Types

TypeTriggerRuns AsContextUse Case
Record-Triggered FlowRecord create/update/deleteSystem (no user interaction)Fires on DML against a specific objectField updates, cross-object automation, validation beyond formula limits
Screen FlowUser launches (button, page, quick action, Experience Cloud)Running userInteractive — collects input, displays outputGuided wizards, data entry forms, case deflection, self-service
Schedule-Triggered FlowTime-based (daily/weekly or record-based schedule)Default process automation userBatch context, runs on a schedule against a set of recordsReminder emails, SLA escalations, periodic data cleanup
Auto-launched FlowInvoked by another flow, Apex, REST API, or processSystem or calling contextNo UI — runs headlesslyReusable logic modules, API-triggered automation, Apex-invoked orchestration
Platform Event-Triggered FlowPlatform event message publishedSystem (event subscriber)Asynchronous, separate transaction from publisherEvent-driven integrations, decoupled processing, near-real-time reactions

Record-Triggered Timing

Decision Tree

code
Record DML fires
  ├─ Before Save ──→ Fast field updates on SAME record only (no DML)
  ├─ After Save (Run Immediately) ──→ DML allowed, same transaction, cross-object updates
  └─ After Save (Run Asynchronously) ──→ Separate transaction, callouts OK, eventual consistency

Timing Comparison

TimingDML AllowedSame TransactionCan Update Other RecordsCalloutsUse Case
Before SaveNoYesNo (same record only)NoDefault field values, field derivation, lightweight validation
After Save (Run Immediately)YesYes (same transaction, post-commit triggers)YesNoCreate child records, update related records, send emails
After Save (Run Asynchronously)YesNo (separate transaction)YesYesHTTP callouts, external system sync, heavy processing

Key rule: Start with Before Save. Escalate to After Save only when you need DML on other records. Escalate to Async only when you need callouts or want isolation from the triggering transaction.

Flow Elements

ElementCategoryPurposeNotes
AssignmentLogicSet variable valuesSupports add, subtract, equals, add to collection
DecisionLogicBranch execution pathEquivalent to if/else; evaluated top-down, first match wins
LoopLogicIterate over a collectionAvoid DML inside loops — assign to collection, then bulk DML after
Get RecordsDataSOQL query (retrieve records)Returns first record or collection; filter conditions map to WHERE clause
Create RecordsDataInsert one or many recordsAccepts single record variable or collection for bulk insert
Update RecordsDataUpdate records matching criteria or from a variableCan filter directly or pass a record/collection variable
Delete RecordsDataDelete records matching criteria or from a variableUse with caution; no recycle bin undo in all contexts
SubflowLogicInvoke another auto-launched or screen flowPass variables in/out; key reusability mechanism
ActionIntegrationInvoke Apex, send email, post to Chatter, call external serviceApex actions = @InvocableMethod; Custom Notifications also available
ScreenUIDisplay form to user, collect inputScreen Flows only; supports dynamic visibility, validation, reactive components
Custom ErrorLogicThrow a fault with a custom messageBefore-save only; blocks the save and displays error to user
WaitLogicPause flow until a condition or timeRequires a Paused interview; not available in before-save
Collection SortDataSort a record collection by field(s)Ascending/descending; sort before display or processing
Collection FilterDataFilter a collection by conditionsReduces collection without another Get Records (saves SOQL)
TransformDataMap fields between collections or reshape dataUseful for converting between object types or restructuring data for subflows

Variable Types

TypeCollection SupportedAvailable as Input/Output
TextYesYes
NumberYesYes
CurrencyYesYes
BooleanYesYes
DateYesYes
DateTimeYesYes
Record (single)No (use Record Collection)Yes
Record CollectionN/A (is a collection)Yes
Picklist ChoiceYesNo (Screen Flow internal use)
StageNoNo (Flow orchestration internal)
Apex-DefinedYesYes

Fault Handling

Fault Connectors

Every data element (Get, Create, Update, Delete) and action element can have a fault connector — a separate path that executes when the element throws a runtime exception. Add one by clicking the element and selecting "Add Fault Path" (or dragging from the fault node).

System Variables for Faults

VariableContents
$Flow.FaultMessageThe runtime error message (e.g., FIELD_CUSTOM_VALIDATION_EXCEPTION: ...)
$Flow.InterviewGuidUnique identifier for the flow interview instance — use for log correlation

Recommended Fault Pattern

code
[Element with fault risk]
  ├─ Success path → continue
  └─ Fault path →
       1. Create a custom Error_Log__c record (or publish a Log_Event__e platform event)
          - Store: $Flow.FaultMessage, $Flow.InterviewGuid, record ID context, timestamp
       2. Optionally notify admin (email alert or custom notification)
       3. Terminate gracefully (do NOT leave the flow dangling)

Best practice: Add fault paths to every DML and callout element. Unhandled faults produce cryptic "Unhandled fault" emails and block the triggering transaction. Logging to a platform event is preferred over a custom object insert because platform event publishes are independent of the current transaction's rollback.

Flow vs Apex Decision Guide

ScenarioFlowApex
Simple field updatesPreferred (Before Save)Overkill
Complex branching logicWorkable but gets unwieldy past ~15 decisionsPreferred — cleaner control flow
Bulk operations (10k+ records)Auto-bulkifies in record-triggered contextPreferred for fine-grained governor control
External calloutsAfter Save (Async) works for simple casesPreferred — retry logic, error handling, named credentials
Admin maintainabilityHigh — visual, low-codeLow — requires developer
ReusabilitySubflows; Apex actions bridge to codeApex classes, @InvocableMethod exposes to flows
TestingLimited (Flow debug, test coverage via triggers)Full Apex test framework, assertions, mocking
Performance at scaleGood for moderate volume; watch governor limitsSuperior — explicit limit management, efficient SOQL
Transaction controlImplicit (before/after save boundaries)Explicit — Savepoint, Database.rollback(), partial success via Database.insert(records, false)

Rule of thumb: If an admin can own it and it stays under ~20 elements, use Flow. If it needs complex iteration, error recovery, or will be called at high volume, use Apex and expose it to Flow via @InvocableMethod.

Best Practices

PracticeDetail
Filter early with entry conditionsSet entry conditions on record-triggered flows to reduce unnecessary executions. Equivalent to an early return in a trigger — check $Record.Status__c = 'Active' before running any logic.
Naming conventionsRecord-Triggered: Object_Trigger_Purpose (e.g., Case_AfterCreate_AssignEntitlement). Screen: Object_Screen_Purpose (e.g., Lead_Screen_QualificationWizard). Subflow: Sub_Purpose (e.g., Sub_SendNotification).
Use subflows for reusabilityExtract shared logic into auto-launched subflows. Pass record IDs or collections as input variables. Keeps parent flows clean and DRY.
Bulkification awarenessRecord-triggered flows auto-bulkify: one interview handles all records in the transaction. But each Get/Create/Update/Delete inside a Loop consumes a SOQL or DML call per iteration — move DML outside loops.
Avoid recursive triggersUse $Record__Prior comparisons in entry conditions to prevent re-entry (e.g., only run when Status__c is changed, not on every update). For cross-object updates that re-trigger, check "When to Run the Flow for Updated Records" and select "Only if the record that triggered the flow to run is updated."
One flow per object per timingAvoid multiple record-triggered flows on the same object + timing. Execution order is nondeterministic. Consolidate into one flow with Decision elements.
Version controlActivate a new version rather than editing the active one. Keep previous version active until the new one is tested. Use Flow description field for change notes.

Common Pitfalls

SymptomCauseFix
Flow triggered recursively / Maximum flow trigger recursion depth exceededFlow updates the same object it triggers on, causing re-entryAdd entry conditions using $Record__Prior field comparisons; check "Only if the record that triggered the flow is updated" under re-evaluation settings
UNABLE_TO_LOCK_ROWMultiple flows/processes competing for the same record in parallel transactionsReduce cross-object updates in synchronous context; use async where possible; avoid parent record updates from child triggers in high-volume scenarios
Unhandled fault in flow / cryptic admin emailDML or callout element failed with no fault connectorAdd fault paths to every DML and action element; log $Flow.FaultMessage to an error record or platform event
Flow interview expired / Flow interview is no longer availableWait element or paused interview exceeded retention (default 30 days) or user re-submitted a stale screenReduce wait durations; handle screen flows with retainOnFinish; inform users of session limits
Too many SOQL queries: 101Get Records inside a Loop element, executed per record in a bulk transactionMove Get Records before the loop (query once, filter in-memory with Collection Filter), or restructure to query a collection
Apex CPU time limit exceeded in flowLarge collection loop with complex assignments, or calling expensive Apex actions per iterationSimplify loop body; move heavy logic to bulkified Apex @InvocableMethod that accepts List<> input; reduce decision elements inside loops
Null reference / The flow tried to access a value on a null recordGet Records returned no results and subsequent elements reference fields on the null variableAdd a Decision element after Get Records to check if the variable Is Null equals {!$GlobalConstant.True} before accessing fields
Collection variable empty after Get RecordsFilter conditions too restrictive, or querying the wrong object/field API nameDebug with Flow Debug tool; verify field API names (not labels); check filter logic (AND vs OR); ensure records actually exist matching criteria