Salesforce Dictionary - Free Salesforce GlossarySalesforce Dictionary
All errors
Flow

The flow failed to access the value for {!variable} because it hasn't been set or assigned

A flow tried to read a variable that no element ever wrote to. Either an upstream Get Records returned nothing (so the result variable stayed null), or a path through the flow skipped the assignment that was supposed to populate it.

Also seen asThe flow failed to access the value for·hasn't been set or assigned·FLOW_ELEMENT_ERROR variable·flow variable not set

A user clicks Submit on a screen flow. The flow shows a generic error page. The fault email lands in the admin's inbox an hour later: The flow failed to access the value for {!myVariable} because it hasn't been set or assigned. The flow worked yesterday. Nothing in the flow definition changed. The only thing that changed was that today's user hit a path through the flow that yesterday's user didn't.

What the runtime is actually complaining about

Every variable, output of a Get Records, output of an Apex action, and output of a screen component in a flow has a value at any given point. The runtime tracks these in an internal interview state. When an element reads {!myVariable} and the interview state has no value bound to myVariable, the runtime throws the unhandled fault.

Two flavors of "no value bound" both look the same to the runtime:

  • The variable was never initialized and nothing wrote to it.
  • A Get Records element returned zero rows, so its output collection is empty (and reading a property of the empty collection fails).

The error message names a specific resource ({!myVariable} in the message), which is where you start. The cause is upstream of the read.

Fingerprint A: the empty Get Records

A Get Records element with the Number of Records: Only the first record option returns a single record reference. If the query finds zero matches, that reference is null. The next element that reads any field off that reference fails.

Get Account by Name
  - Object: Account
  - Conditions: Name Equals {!varName}
  - Store Output: First Record
  - Variable: {!foundAccount}

Then later:
  Update Records: Set {!foundAccount}.Industry = 'Technology'
  → Fails with "The flow failed to access the value for {!foundAccount} ..."
  if no Account matched.

The fix is to add a Decision element right after the Get Records that branches on Is Null for the result. The "null" outcome routes to a fault path (display a polite error, log to a custom object, or skip the rest of the flow). The "not null" outcome continues with the work.

A second option: change the Get Records to Store all records with output type Collection. The collection is empty (not null) when no rows match. Reading {!foundAccounts.size} returns zero without throwing. You then loop or guard on the collection size.

Fingerprint B: a conditional path that skips an assignment

The flow has two branches:

  • Branch 1 sets {!flagShouldEmail} to true via an Assignment element.
  • Branch 2 doesn't assign the variable at all.

Both branches converge into a Decision that reads {!flagShouldEmail}. The convergence node fires from Branch 2 with the variable still unassigned. The Decision fails to read it. Fault.

The fix is one of two patterns.

Initialize at the top. Add an Assignment element as the first thing in the flow that gives every variable a known starting value. False for booleans, 0 for numbers, empty string for text, empty collection for collections. The cost is one element; the benefit is immunity to this entire class of fault.

Assign on every branch. If you don't want a global initialization step, every branch that converges back must set the variable on its way. Branch 2 needs an Assignment that puts {!flagShouldEmail} to false (or whatever the branch's natural value would be).

The global initialization pattern wins almost every time. It's one element, lives at the top of the flow where it's discoverable, and protects against future branch additions without revisiting the convergence math.

Debugging without guessing

Flow Builder includes a Debug mode that's the fastest way to find the offending element.

  1. Open the flow in Flow Builder.
  2. Click Debug in the top right.
  3. Pick inputs that reproduce the failure case (or as close as you can get for a record-triggered flow).
  4. Step through. The right-hand panel shows every variable's value at each stage.
  5. The first element where a variable's value reads "Not set" or shows the missing assignment is your culprit.

For record-triggered flows, Debug needs a sample record id. Pick the id of the actual record that triggered the production failure (the fault email includes it) and replay against that. The interview will reach the same path and the same null read.

For flows that can't be reproduced in Debug (because the trigger event is something Debug can't simulate cleanly), the Flow Trigger Explorer under Setup → Process Automation Settings shows the interview history of every recent invocation. Pick the failed one, expand the interview log, and find the element that reads the missing variable.

The broken example

A screen flow that displays an account summary, with a hidden bug on the "no match" path:

Screen 1: Ask for account name
  - Input: {!searchName}

Get Account
  - Where Name Equals {!searchName}
  - Store First Record into {!matchedAccount}

Screen 2: Show details
  - Display: "Industry: {!matchedAccount.Industry}"
  - Display: "Revenue: {!matchedAccount.AnnualRevenue}"

If the search returns nothing, {!matchedAccount} is null and Screen 2 fails with the missing-variable fault. The user sees a generic error page. The admin gets the email. The flow looks broken even though the only "bug" is that someone typed a name that doesn't match a real Account.

The fixed example

The same flow with a Decision element guarding the screen and an initialization step at the top:

Start

Assignment: Initialize defaults
  - {!matchedAccount} = (skip, set after Get)
  - {!searchAttempts} = 0
  - {!hasMatch} = false

Screen 1: Ask for account name
  - Input: {!searchName}

Get Account
  - Where Name Equals {!searchName}
  - Store First Record into {!matchedAccount}

Decision: Did we find one?
  - Outcome "Yes": {!matchedAccount} Is Null = false
    → Assignment: {!hasMatch} = true
    → Screen 2: Show details
       - "Industry: {!matchedAccount.Industry}"
       - "Revenue: {!matchedAccount.AnnualRevenue}"
  - Outcome "No" (default):
    → Screen 2b: Apologize politely
       - "No Account named {!searchName} was found. Try a different spelling."
       - Button: Search again → loops back to Screen 1

Two improvements: the Decision protects every downstream read of {!matchedAccount}, and the user gets a useful message instead of a crash on the empty path. The initialization step at the top documents which variables exist and what their starting values are.

Subflow and Apex-action outputs are the same trap

When a flow calls a subflow or an Apex action, the subflow's output variables are merged into the parent's interview state. If the subflow ran an internal branch that didn't assign an output variable, the parent reading that output sees "not set" and fails.

The fix is the same as for in-flow variables: every output of a subflow or Apex action should be assigned on every path within that subflow / action. Subflows that wrap external systems benefit from a wrapper that always returns a populated success boolean and a populated errorMessage string, even on the happy path. The parent flow can then read either field without checking for null.

Why faults are easy to miss

Flow faults don't surface to the user by default in record-triggered or scheduled flows. The interview throws, the platform writes a log entry, and the work that was supposed to happen silently doesn't happen. The user clicks Save, sees the record save fine, and never knows the post-save automation failed halfway through.

The fix is to wire every potentially-failing element to a Fault path that:

  1. Logs to a custom Flow_Error__c object with the interview id, the element name, and the error message.
  2. Sends an email or Slack notification to a known admin alias.
  3. Optionally surfaces a screen message if the flow is user-facing.

A flow without explicit fault handling fails silently. A flow with explicit fault handling fails loudly. The difference is a fifteen-minute investment that saves countless hours of "the report numbers look wrong but I don't know why."

A defensive habit that prevents most cases

Default-initialize every variable in the flow at the top. Add an Assignment element labeled "Set defaults" that lives immediately after the Start element. Set:

  • Booleans to false.
  • Integers and Decimals to 0.
  • Text variables to empty string (or a sentinel like '(unknown)').
  • Collections to a new empty collection.
  • Record references stay null but you mark them with a comment.

This costs one element per flow. It immunizes the flow against the entire "unassigned in some branch" failure mode. Future authors adding new branches automatically inherit the safety.

Differences between record-triggered and screen flow handling

A record-triggered flow's failure path is invisible to the end user; the fault is captured server-side and the original DML operation may or may not complete depending on whether the trigger is "before save" or "after save." A screen flow's failure path shows a generic error page to the user, which is frustrating but at least visible.

The diagnostic implication: record-triggered flows need the most aggressive fault-handling discipline. A silent failure in a save-time automation can corrupt data quietly for weeks before someone notices. A screen flow failure breaks the user experience immediately and gets reported.

For record-triggered flows, treat every Get Records, every Apex action, and every Subflow call as a candidate for a fault path that logs to a custom error table. The table doesn't need to be fancy: a Flow_Error__c object with fields for the interview id, the timestamp, the failing element name, and the user-friendly error message is enough. Build a simple list view filtered to the last 24 hours and let your admin team check it weekly.

What changes in Spring 26 and after

Salesforce continues to evolve flow's error-reporting surface. Recent releases have added structured fault objects on Apex-invocable actions, automatic null guards on certain element types, and more granular control over which elements emit fault paths versus crash the interview. These improvements don't change the fundamental rule (read a variable that was never assigned, you fault), but they do reduce the cases where you need to hand-roll the guard.

The general direction is toward flows that behave more like Apex methods, with explicit return-value handling on every action. Lean into that pattern: write flows where every element either always assigns its output or routes through a fault path. Flows authored that way age well across releases.

Further reading from Salesforce

Related dictionary terms

Share this fix

Share on LinkedInShare on X

Related Flow errors