Skip to the content
GigaJSON
All tools
Format and check
View and query
Convert
Encode and decode
Sample files
Guides Open GigaJSON

n8n $json expressions explained: paths, items and $jmespath

· 5 min read · By GigaJSON

If you build workflows in n8n, you write expressions like {{ $json.query }} all the time. This guide explains what each part means, how to write the path to the field you want, how to reach data from other nodes, and what the common errors mean. Every example uses syntax from n8n's own documentation.

What {{ $json.field }} means

An expression is JavaScript inside double curly braces, placed in a node's parameter. When the workflow runs, n8n replaces it with the result. $json is the JSON data of the item the node is working on.

Say a Webhook node received this:

[
  {
    "headers": { "host": "n8n.instance.address" },
    "params": {},
    "query": {},
    "body": { "name": "Jim", "age": 30, "city": "New York" }
  }
]

In the next node, {{ $json.body.city }} gives New York. You can write the same thing with brackets: {{ $json['body']['city'] }}.

A node works through its input items one at a time. With 50 items coming in, {{ $json.body.city }} is worked out 50 times, once with each item's data. That's why you rarely need an index such as [0] at the start: $json is already one item. (AI sub-nodes are the exception: their expressions always use the first item.)

Why saved workflows show ={{ ... }}

Open an exported workflow file and a field with an expression looks like this:

"text": "={{ $json.text }}"

The = at the start marks the string as an expression rather than plain text. n8n's own docs write expression values the same way, for example ={{ $secrets.apiKey }} in a credential.

In the editor you don't type the =. Switch the field from Fixed to Expression and write {{ $json.text }}, or drag a field in from the input panel, which switches the field and writes the expression for you.

Writing the path to a field

Paths follow JavaScript:

You want Expression
A top-level field {{ $json.status }}
A nested field {{ $json.customer.email }}
An item in a list {{ $json.items[0].sku }}
The last item in a list {{ $json.items[$json.items.length - 1].sku }}
A key with a space or a dash {{ $json["first name"] }} or {{ $json["order-id"] }}
How many items a list has {{ $json.items.length }}

A key with a space, a dash, a dot or a leading digit can't follow a dot. Write it in brackets with quotes.

The quickest way to get a long path right is to click it. Copy the node's output from n8n's JSON view, paste it into the JSON Path Finder and click the value you need. The n8n line gives the expression ready to paste, such as {{ $json.body.city }}, with any awkward keys already quoted. It leaves out the first [0] of a pasted list for the reason above.

Data from other nodes

$json only sees the current node's input. For anything else, n8n has these:

Expression Gives you
$("Webhook").item.json.body.city The field from the Webhook item that led to the current item
$("Webhook").first().json.body.city The field from the Webhook node's first item
$("Webhook").last().json The last item's data
$("Webhook").all() Every item, as a list
$input.all() Every item coming into the current node

The node name goes in quotes, exactly as it appears on the canvas. Note the .json after .item, .first() and .last(): they return the whole item, and its data sits under json.

.item is usually what you want, because it follows each item back to the one it came from. n8n calls this item linking. It fails when that link is broken, when one item came from several, or when the node is on another branch. Then use .first(), .last() or .all()[index] instead, if you know where the item sits.

Defaults and conditions

n8n adds two helpers:

{{ $ifEmpty($json.nickname, $json.name) }}
{{ $if($json.total > 100, "large", "small") }}

$ifEmpty returns the second value when the first is undefined, null, an empty string, an empty list or an empty object. A plain JavaScript ternary works too: {{ $json.total > 100 ? "large" : "small" }}.

Querying with $jmespath()

For anything a path can't express, such as filtering a list or picking several fields from each item, n8n includes JMESPath. Say $json.body.people is:

[
  { "first": "James", "last": "Green" },
  { "first": "Jacob", "last": "Jones" },
  { "first": "Jayden", "last": "Smith" }
]
Expression Result
{{ $jmespath($json.body.people, "[*].first") }} ["James", "Jacob", "Jayden"]
{{ $jmespath($json.body.people, "[:2].first") }} ["James", "Jacob"]
{{ $jmespath($json.body.people, "[].[first, last]") }} [["James", "Green"], ["Jacob", "Jones"], ["Jayden", "Smith"]]
{{ $jmespath($json.body.people, "[?last=='Jones'].first | [0]") }} "Jacob"

Watch the argument order. n8n's $jmespath(object, searchString) takes the data first. The JMESPath specification and most libraries take the search string first, so swap the two when you copy an example from elsewhere.

To try a search string before you put it in a workflow, paste the data into the JMESPath playground and type the part in quotes, such as [?last=='Jones'].first | [0]. The result updates as you type.

Longer code in one expression

An expression is one JavaScript expression. To use variables or several statements, wrap the code in a function that calls itself. This one uses Luxon, the date library n8n includes, to count the months between two dates:

{{(()=>{
  let end = DateTime.fromISO('2017-03-13');
  let start = DateTime.fromISO('2017-02-13');
  let diffInMonths = end.diff(start, 'months');
  return diffInMonths.toObject();
})()}}

If it grows beyond a few lines, a Code node is easier to read and test.

Common errors

Can't get data for expression, or Referenced node is unexecuted. The expression points at a node that hasn't run yet. When you test a single node, run the workflow up to the named node first. In code, $("Node name").isExecuted tells you whether it ran.

Invalid syntax. Often a stray character, such as the trailing dot in {{ $('If').item.json. }}. The expression editor's preview shows the result or the error as you type.

The 'JSON Output' in item 0 contains invalid JSON. The node is in JSON mode and what you built isn't valid JSON. Usually a value from an expression broke it: a string with a quote in it, or a field that's missing on some items. Wrap strings with .toJsonString(), which adds the quotes and escapes what's inside:

{
  "message": {{ $json.message.toJsonString() }},
  "count": {{ $json.items.length }}
}

To find what's wrong with a JSON body, paste the output into the JSON validator: it shows the line and column of the first error. JSON Repair fixes common mistakes, such as missing commas or quotes, and lists each change.

Questions

What does {{ $json.query }} mean in n8n?

It's an n8n expression. $json is the JSON data of the item the node is processing, so {{ $json.query }} is replaced by that item's query field when the workflow runs.

Why does my workflow JSON show ={{ $json.field }}?

In a saved or exported workflow, a parameter that holds an expression is stored as a string starting with =. In the editor you don't type the =; you switch the field to expression mode and write {{ $json.field }}.

How do I get a value from an earlier node?

Use $("Node name").item.json.field for the item linked to the current one, or .first(), .last() and .all() when you know the item's position.

Why are the arguments of $jmespath() the other way round?

n8n's $jmespath(object, searchString) takes the data first. The JMESPath specification and most libraries take the search string first, so swap them when you copy an example.

More from the blog