Expressions are the {{ }} snippets that pull data into node fields. They are short, so when one fails the error feels out of proportion: the whole node stops. Most failures fall into five families, and each has a clear fix.
In short. "Referenced node is unexecuted" and "Can't get data for expression" mean the node you reference has not run yet in this execution: run the workflow up to it, rewire so it runs first, or guard with $('Node').isExecuted. "Invalid JSON" means the JSON body is not valid once the expression is filled in. Webhook data lives under $json.body, not $json.
1. "Referenced node is unexecuted" / "Can't get data for expression"
The documentation explains that n8n cannot retrieve data from a node that has not run in the current execution. The full message names the node:
An expression references the node 'Get customer', but it hasn't been executed yet. Either change the expression, or re-wire your workflow to make sure that node executes first.
Typical causes:
- you are testing one node on its own, and the referenced node never ran in this test;
- the referenced node is on another branch of an IF or Switch that did not run;
- the referenced node comes after the current one.
Fixes:
- 1 Test the workflow up to the named node first, or pin its data.
- 2 Move the referenced node before the current one on the same path.
- 3 Guard the expression when the node is optional:
2. "The 'JSON Output' in item 0 contains invalid JSON"
You used JSON mode (in Edit Fields, HTTP Request body and so on) and the result is not valid JSON. Two common reasons:
- a syntax error: missing quotes, a trailing comma, unbalanced brackets;
- an expression that inserts a value which breaks the JSON, for example text containing quotes, or
undefinedwhen a field is missing on some items.
Safer pattern for text values: let n8n encode them.
JSON.stringify adds the quotes and escapes special characters, so a name like O"Neil cannot break the body. Check the final JSON in a JSON linter, or compare a working and a failing body with our free JSON Diff Checker.
3. "Invalid syntax"
The expression itself is malformed. The documentation's example is a trailing dot: {{ $('If').item.json. }}. Other frequent ones: a missing closing }}, single { } instead of double, or a JavaScript keyword used as a field name without brackets. Test the expression in isolation with our n8n Expression Tester.
4. The value is empty although the data is there
Usually a wrong path rather than an error:
- Webhook data arrives as
body,headers,paramsandquery. The email is{{ $json.body.email }}. - Field names with spaces or dashes need brackets:
{{ $json["first name"] }},{{ $json["next-page"] }}. - Arrays need an index:
{{ $json.contacts[0].email }}.
Paste a sample of the input into our JSON Path Finder to get the exact path.
5. Paired item errors
When a node references $('Node').item but n8n cannot tell which input item produced the current one, you get errors about paired items. It happens after Code nodes that change the number of items, or after merges. Fixes:
- use
.first(),.last()or.all()when you do not need the matching item; - in Code nodes that create new items, keep the link by returning items derived from the input ones (the documentation's item linking section covers
pairedItem); - restructure so the data you need is carried in the item itself, with an Edit Fields node, instead of looking back.
Writing longer logic in an expression
For multi-line logic, the documentation shows an immediately invoked function:
If the expression grows beyond a few lines, move it to a Code node or an Edit Fields node: it is easier to test and to read.
Worked example: one email node, two branches
A common workflow: a webhook receives a form, an IF checks whether the contact exists in the CRM, and both branches end in the same "Send email" node that greets the person by name.
- True branch: "Get contact" (CRM) runs, so
$('Get contact')has data. - False branch: "Create contact" (CRM) runs instead, and "Get contact" never runs.
If "Send email" uses {{ $('Get contact').item.json.firstname }}, every new contact triggers "Referenced node is unexecuted". Three clean fixes:
- 1 Guard the expression:
- 1 Normalise before merging: add an Edit Fields node at the end of each branch that outputs the same field (
firstname), then read{{ $json.firstname }}in the email node. - 2 Read from the webhook, which always ran:
{{ $('Webhook').item.json.body.firstname }}.
Option 2 is the most maintainable: each branch is responsible for producing the same shape, and the shared node does not need to know which branch ran.
Expressions, cheat sheet
| Need | Expression |
|---|---|
| Current item field | {{ $json.email }} |
| Field with a space | {{ $json["first name"] }} |
| From a named node, linked item | {{ $('Get contact').item.json.email }} |
| First item of a node | {{ $('Get contact').first().json.email }} |
| Default if missing | {{ $json.phone ?? 'not provided' }} |
| Today, formatted | {{ $now.toFormat('yyyy-LL-dd') }} |
| Number of items from a node | {{ $('Get contact').all().length }} |
| Safe JSON text | {{ JSON.stringify($json.note) }} |
Paired items, explained simply
When you write $('Node').item, n8n finds the item in that node that produced the current one, by following pairedItem links. Most nodes keep these links automatically. They break when a Code node creates brand new items, or after some merges, because n8n can no longer tell which input produced which output. Then use .first(), .last() or .all() when you do not need the matching item, or carry the needed field forward on the item itself with an Edit Fields node.
Troubleshooting table
| Message | Cause | Fix |
|---|---|---|
| Referenced node is unexecuted | Node did not run in this execution | Guard with isExecuted, rewire, or normalise branches |
| Can't get data for expression | Same family; often testing one node alone | Run the workflow up to the node, or pin data |
| Invalid syntax | Malformed expression | Check {{ }}, dots and brackets |
| JSON Output contains invalid JSON | Inserted values broke the JSON | JSON.stringify() each inserted value |
| Paired item data unavailable | Item links lost | .first() / .all(), or carry data forward |
| Value is empty | Wrong path | $json.body... for webhooks; brackets for special names |
Frequently asked questions
What does "Referenced node is unexecuted" mean in n8n?
The expression reads a node that has not run in the current execution. Run it first, rewire the workflow, or check $('Node').isExecuted before reading it.
Why is my n8n expression returning undefined?
The path is wrong for this data. Webhook fields are under $json.body, names with spaces need brackets, and arrays need an index.
How do I avoid invalid JSON when inserting text with an expression?
Wrap values with JSON.stringify(), which adds quotes and escapes characters such as quotes and line breaks.
Can I write several lines of JavaScript in an n8n expression?
Yes, wrapped in an immediately invoked function, as shown in the n8n documentation. For longer logic, prefer a Code node.
What is the difference between .item and .first() in n8n expressions?
.item returns the linked item that produced the current one. .first() always returns the first item of that node. Use .item when each item must match its source.
How do I check whether a node ran in an expression?
Use $('Node name').isExecuted, which returns true or false.
Why does my expression work in test but fail in production?
In a test you may run the whole path, so every node has data. In production, branches skip nodes, and some fields are missing on some items. Guard both cases.
Tags
Steve founded PURIST, a workflow automation studio working with n8n, Make and Claude AI, and is the point of contact on every project. Prices and features in our articles come from vendor documentation and public pricing pages. How we research and score tools.