Debugging expressions
When an expression can't be resolved, Flero leaves a marker in the value instead of failing silently. Read the marker, fix the expression, run again.
![The JSON tab of a Log node after a run. The message field reads Result: [EXPR-ERROR: Exception: Node "Get ordr" not found ... Available: [Start, Get order].], because the expression misspells the node name.](/assets/shots/expressions__expr-error.light.webp)
![The JSON tab of a Log node after a run. The message field reads Result: [EXPR-ERROR: Exception: Node "Get ordr" not found ... Available: [Start, Get order].], because the expression misspells the node name.](/assets/shots/expressions__expr-error.dark.webp)
The [EXPR-ERROR: …] marker
Every block that fails is replaced with:
[EXPR-ERROR: <reason>]The marker uses square brackets, not braces, so no later step can mistake it for an expression and strip it. Other blocks in the same field still resolve.
| Reason | What it means | Fix |
|---|---|---|
| The node isn't found; the message lists the available names | A typo, or a node that didn't run | Copy the exact display name from the canvas |
unknown reference "start.output.x" — expected $node["Name"], $trigger, $workflow, $input, or $env |
A node path written without the $node["…"] wrapper |
Write $node["Start"].output.x |
| Cannot access a property on a null value | A field earlier in the path is missing | Check the upstream output; build the path one step at a time |
| Cannot access a property on a non-object value | The path goes into text or a number, for example .length or .toLowerCase() |
Remove the method; do the work in a node (see Functions) |
| Cannot index a non-array value, or index out of bounds | [n] on something that isn't a list, or past its end |
Check the list exists and is long enough |
An unset workspace variable is different: {{ $env.NAME }} for a variable that doesn't exist fails the node before it runs, with "Workspace variable "NAME" is not set."
When nothing is flagged
A {{ … }} block with no $ root is not treated as an expression. It stays in the value as literal text, with no marker. Common slips:
{{ input.email }} // left as text: write {{ $input.email }}
{{ $json.id }} // left as text: write {{ $input.id }}
{{ $now }} // left as text: not a root
{{ credentials.apiKey }} // left as text: use a saved credentialIf you see {{ in a node's resolved input or output, look for a missing $ root.
A few nodes (If, Switch in expression mode, Set, While, Loop, Log, Workflow Control and Start) take a second look at such a block and read it as a field of the current item. So {{ status }} in an If condition reads the item's status. That can hide the mistake in those nodes. Use $input or $node["Name"], which work in every field.
Strategy 1: insert references, don't type them
Open the expression editor on a field and use the Data Mapper. It shows every earlier node's real output and your workspace variables. Click a field and Flero inserts the right $node["…"] path.
You can also drag any field from a node's Output tab in the inspector into an expression input. In the Code node, the <> icon opens the same picker.


Strategy 2: run, inspect, iterate
- Run the workflow once (or test the upstream node) so it has output.
- Open the upstream node's Output tab.
- Find the field you want and note its full path, or drag it into the field that needs it.
Strategy 3: build the path one step at a time
For a long path, start with the whole output and add one segment at a time, running each step:
{{ $node["API"].output }}
{{ $node["API"].output.response }}
{{ $node["API"].output.response.body }}
{{ $node["API"].output.response.body.users[0].email }}The step where it breaks tells you which segment is wrong.
Common pitfalls
Forgetting $node["…"]
{{ start.output.x }} // ❌ [EXPR-ERROR: unknown reference …]
{{ $node["Start"].output.x }} // ✅Hyphenated names (my-fetch) aren't caught by this check, and stay as literal text. Always use the $node["my-fetch"] form.
Case sensitivity
// The node is named "HTTP Request"
{{ $node["HTTP request"].output.response.body }} // ❌ node not found
{{ $node["HTTP Request"].output.response.body }} // ✅Referencing a node that didn't run
If the node is on the true branch of an If and the If went false, the node has no output, and references to it fail. Reference it only on the branch where it runs, or move the reference above the If.
$trigger.body instead of $trigger.payload
A webhook or form body is under $trigger.payload. $trigger.body.x reads a property of something that isn't there and fails.
HTTP Request data is under response.body
{{ $node["Get User"].output.email }} // usually not there
{{ $node["Get User"].output.response.body.email }} // ✅null shows as the text null
Hello, {{ $trigger.payload.name }} // name missing → "Hello, null"Give the field a default first with a Set node (allowOverwrite off). See Common patterns.
Comparing in an If
Values from $ expressions arrive as text. equal compares exactly, and a missing field arrives as the text null, so isNull is false for it. See Operators → Values arrive as text.
Logging for debug
Drop a Log node mid-workflow to see what you're working with:
message: DEBUG upstream output: {{ $node["HTTP Request"].output }}
level: debugOr use a Code node that returns what you want to see: return { seen: input[0], count: input.length };. Then open the run's execution detail.
Tips & gotchas
- A marker travels. If a node receives a value with
[EXPR-ERROR: …]in it, the marker stays in the text. Treat it as a bug to fix, not a warning. - The Data Mapper prevents most typos. Reach for it first.
- When in doubt, look at the output. It is faster than guessing.
Related
Found something out of date? This page lives in the Flero docs content set.