Common Mistakes
Most variable scoping errors produce silent null values rather than runtime exceptions. The expression evaluates to null, downstream nodes receive unexpected input, and the workflow may complete successfully while producing wrong results. Understanding these patterns is critical for reliable workflow design.
Mistake 1 — Using $output Before the Node Has Run
This is the most frequent variable scoping error. A node references $output.{nodeId} for a node that is downstream, on a different branch, or has been skipped due to an error.
| Wrong | Right |
|---|---|
Referencing $output.approval-node in a node that runs before approval-node in the graph |
Only reference $output.{nodeId} for nodes that are topologically upstream — verified in the Flow Studio canvas |
Referencing $output.branch-b-node from branch A (parallel branches — branch B's output is not available to branch A) |
Read cross-branch data only after a fan-in (merge) node where both branches have completed |
// WRONG — approval-node is downstream of this node
{
"approvalDecision": "{{$output.approval-node.decision}}" // Always null here
}
// RIGHT — read the decision only in a node AFTER approval-node in the graph
{
"approvalDecision": "{{$output.approval-node.decision}}" // Valid — node is upstream now
}
Mistake 2 — Using $input at the First Node
The first node after the trigger has no upstream node — $input is always null there. Workflow designers often assume $input refers to the trigger payload, but that is $json.
// WRONG — at the first node, $input is null
$input.customerId // null — always
// RIGHT — use $json for the trigger payload at the first node
$json.body.customerId // Correct
Mistake 3 — Using $var Before the SetVariable Node Has Run
Variables set by SetVariable nodes are only available downstream of that node in the graph. If a node references $var.myVar and the SetVariable that sets myVar is on a parallel branch or downstream, the result is null.
// WRONG — SetVariable for 'selectedRegion' is AFTER this node in the graph
"https://api.{{$var.selectedRegion}}.example.com" // Renders as "https://api.null.example.com"
// RIGHT — ensure the SetVariable node runs upstream of any node that reads the variable
Mistake 4 — Treating $global as Execution-Level State
Tenant globals ($global) are tenant-wide constants, not execution-level variables. Attempting to use a SetVariable node to update something that should be in $global will not work — SetVariable writes to the execution-scoped variable store, not the tenant global store.
// WRONG — trying to update a tenant global from a workflow (not supported)
// SetVariable: variableName = "global.taxRate", value = 0.18 — this sets $var.global.taxRate, NOT $global.taxRate
// RIGHT — $global is configured by a tenant administrator in the platform settings, not from within a workflow
Mistake 5 — Confusing nodeId vs. node label
Node labels are what appear in the canvas UI. Node IDs are the internal identifiers used in $output.{nodeId} expressions. A node labelled "Fetch Customer" may have the ID node-4 or fetch-customer depending on whether the designer renamed it.
// The node is LABELLED "Fetch Customer" but its ID is still "node-4" (default, not renamed)
$output.fetch-customer // null — nodeId is actually "node-4", not "fetch-customer"
$output.node-4 // Correct — but fragile
// BEST PRACTICE — rename the node in Flow Studio to a meaningful ID, then use that ID
// Rename to "fetch-customer" → then $output.fetch-customer works correctly
Mistake 6 — Using $json Inside a Sub-Workflow to Access Parent Data
Inside a sub-workflow, $json is the sub-workflow's own trigger payload (the data passed by the parent Call node). The parent workflow's $json is not accessible. Explicitly pass parent data through the sub-workflow call node's input configuration.
// WRONG — inside sub-workflow, trying to access parent's trigger payload
$json.body.originalOrderId // null — this is the sub-workflow's $json, not the parent's
// RIGHT — the parent Call node should explicitly pass data to the sub-workflow:
// Call Node config: inputMapping: { "originalOrderId": "$json.body.originalOrderId" }
// Inside sub-workflow: $json.originalOrderId ← receives the mapped value
Diagnostic Approach
When an expression produces unexpected null values:
- Open the Node Inspector in the Observer Panel for the node that has the expression.
- Check the Input Data section to confirm what the node actually received.
- Navigate to the referenced upstream node's Inspector and verify its Output Data section shows the expected value.
- Confirm that the referenced node's ID in
$output.{nodeId}matches the actual nodeId visible in the node configuration panel — not its label.