n8n expressions cheat sheet: $json, $('Node'), dates and more
An n8n expressions cheat sheet with real outputs: $json, $input, $('Node'), strings, arrays, dates with Luxon, JMESPath, and fixes for common errors.

An n8n expression is a line of JavaScript inside {{ }} that n8n evaluates for each item. The ones you will use most are {{ $json.field }} for the current item, {{ $('Node Name').item.json.field }} for data from an earlier node, {{ $now }} and {{ $today }} for dates, and n8n's helper methods such as .toDateTime(), .toTitleCase() and .sum().
Every example on this page was run in n8n 2.43.7 on October 18, 2026, and the result shown is what n8n returned. The n8n expression reference lists every method; this page covers the ones you need daily, plus the errors you will hit.
The short version
| You want | Expression |
|---|---|
| A field from the current item | {{ $json.email }} |
| A field with spaces or dashes in its name | {{ $json['first name'] }} |
| A field from an earlier node | {{ $('Webhook').item.json.body.email }} |
| The first or last item of a node | {{ $('Webhook').first().json.id }}, .last() |
| All items of the current input | {{ $input.all() }} |
| A default when a field is missing | {{ $json.note ?? 'no note' }} |
| Now, and today at midnight | {{ $now }}, {{ $today }} |
| A date string as a date | {{ $json.createdAt.toDateTime().format('yyyy-MM-dd') }} |
| Multiple statements | {{ (() => { const x = 1; return x + 1; })() }} |
The sample data behind the examples
The examples read from a node named Webhook that outputs items shaped like Webhook node output, with the request data under body (see n8n's webhook example). Results are for the first of three items:
{
"query": { "source": "form" },
"body": {
"name": "ada lovelace",
"email": "Ada@Example.com",
"amount": "49.90",
"tags": ["vip", "beta", "vip"],
"lines": [
{ "sku": "A1", "qty": 2, "price": 10 },
{ "sku": "B2", "qty": 1, "price": 29.9 }
],
"message": "Please reply to Ada@Example.com today",
"html": "<p>Hello <b>ada lovelace</b></p>",
"payload": "{\"source\":\"landing-page\"}",
"createdAt": "2026-10-14T09:30:00Z"
}
}The node that runs the expressions sits after an Edit Fields (Set) node named Prepare, which outputs id ("cus_1"), total (49.9) and items (the lines array). So $json means Prepare's output, and Webhook's data is reached with $('Webhook').
In the tables below, the expression is the part you write inside {{ }}.
Expression basics
- Switch the field to Expression. In any node parameter, switch from Fixed to Expression, then type the expression or drag a field from the INPUT pane into it.
- It's JavaScript. Operators, template literals,
?.,??,.map()and.filter()all work. n8n adds its own variables ($json,$input,$now) and helper methods on strings, numbers, arrays, objects and dates. - One expression, one value. For several statements, wrap them in an immediately invoked function, as n8n's docs show:
{{ (() => {
const lines = $('Webhook').item.json.body.lines;
const total = lines.reduce((sum, l) => sum + l.qty * l.price, 0);
return total.toFixed(2);
})() }}Result: "49.90". If you need more than a few lines, a Code node is usually easier to read and debug.
Current item: $json and $input
| Expression | Result |
|---|---|
$json.total | 49.9 |
$json['total'] | 49.9 |
$json.items.length | 2 |
$json.missing?.field | undefined (no error) |
$input.item.json.id | "cus_1" ("cus_2" for the second item) |
$input.first().json.id | "cus_1" for every item |
$input.all().length | 3 |
$input.all().map(i => i.json.total).sum() | 179.9 |
$itemIndex | 0 (1 for the second item) |
$runIndex | 0 |
$json is shorthand for $input.item.json, the item currently being processed. Expressions run once per item, so $json and $input.item change from item to item, while $input.first() and $input.all() return the same thing for every item.
Data from other nodes: $('Node Name')
Refer to any node that ran earlier with $('Node Name'), using the node's name exactly as shown on the canvas:
| Expression | Result |
|---|---|
$('Webhook').item.json.body.email | "Ada@Example.com" ("grace@example.org" for the second item) |
$('Webhook').first().json.body.name | "ada lovelace" |
$('Webhook').last().json.body.name | "alan turing" |
$('Webhook').all().length | 3 |
$('Webhook').isExecuted | true |
$('Webhook').params.mode | "runOnceForAllItems" (the node's own settings) |
.item returns the matching item: in the docs' words, "the one used to produce the current item in the current node." n8n tracks this through each node, so the second item here gets the second Webhook item. Use .first(), .last() or .all() when you want a fixed item or all of them, regardless of which item is running.
If the name is misspelled or the node was deleted, the expression fails with "Referenced node doesn't exist."
Conditions and defaults
| Expression | Result |
|---|---|
$json.total > 50 ? 'big order' : 'small order' | "small order" ("big order" for the 120 order) |
$if($json.total > 50, 'big order', 'small order') | Same as above |
$json.missing ?? 'default' | "default" |
$ifEmpty($json.note, 'no note') | "no note" |
$('Webhook').item.json.body.missingList?.length ?? 0 | 0 |
$max(3, 9, 4) | 9 |
?? only replaces null and undefined. $ifEmpty() also replaces empty strings, empty arrays and empty objects, per the root reference.
Strings
Prefix each with $('Webhook').item.json.body.:
| Expression | Result |
|---|---|
email.toLowerCase() | "ada@example.com" |
email.isEmail() | true |
email.extractDomain() | "Example.com" (case is kept) |
email.split('@')[1] | "Example.com" |
message.extractEmail() | "Ada@Example.com" |
name.toTitleCase() | "Ada Lovelace" |
name.toUpperCase() | "ADA LOVELACE" |
name.toSnakeCase() | "ada_lovelace" |
name.split(' ')[0] | "ada" |
html.removeTags() | "Hello ada lovelace" |
payload.parseJson().source | "landing-page" |
name.urlEncode() | "ada%20lovelace" |
name.base64Encode() | "YWRhIGxvdmVsYWNl" |
name.hash('sha256').slice(0, 12) | "177f85df57ad" |
message.replaceAll(' ', '-').slice(0, 12) | "Please-reply" |
To build a sentence, a template literal reads better than +:
{{ `Hello ${$('Webhook').item.json.body.name.toTitleCase()}` }}Result: "Hello Ada Lovelace".
Numbers
| Expression | Result |
|---|---|
$('Webhook').item.json.body.amount + 1 | "49.901" (string concatenation) |
$('Webhook').item.json.body.amount.toNumber() + 1 | 50.9 |
$('Webhook').item.json.body.amount.toNumber().round() | 50 |
(1234.5678).round(2) | 1234.57 |
(1234.5678).toFixed(2) | "1234.57" (a string) |
(1234.5).format('en-US', { style: 'currency', currency: 'USD' }) | "$1,234.50" |
Numbers that arrive from webhooks, forms and spreadsheets are often strings. Convert them with .toNumber() before doing math, or + joins text instead of adding.
Arrays
Prefix each with $('Webhook').item.json.body., except the last three:
| Expression | Result |
|---|---|
tags.removeDuplicates() | ["vip","beta"] |
tags.join(', ') | "vip, beta, vip" |
tags.includes('vip') | true |
tags.isEmpty() | false |
lines.pluck('sku') | ["A1","B2"] |
lines.first().sku | "A1" |
lines.map(l => l.qty * l.price).sum() | 49.9 |
lines.filter(l => l.qty > 1).length | 1 |
lines.map(l => l.sku + ' x' + l.qty).join('\n') | "A1 x2\nB2 x1" |
$json.items.map(i => i.price).max() | 29.9 |
[1, 2, 3, 4, 5].chunk(2) | [[1,2],[3,4],[5]] |
[{ field: 'plan', value: 'pro' }, { field: 'seats', value: 5 }].smartJoin('field', 'value') | {"plan":"pro","seats":5} |
Objects
| Expression | Result |
|---|---|
$('Webhook').item.json.body.keys().slice(0, 3) | ["name","email","amount"] |
$('Webhook').item.json.body.hasField('email') | true |
$('Webhook').item.json.body.removeField('html').hasField('html') | false |
({ a: 1, b: null, c: '' }).compact() | {"a":1} |
({ ...$('Webhook').item.json.query, plan: 'pro' }) | {"source":"form","plan":"pro"} |
$('Webhook').item.json.body.lines.toJsonString() | "[{\"sku\":\"A1\",\"qty\":2,\"price\":10},{\"sku\":\"B2\",\"qty\":1,\"price\":29.9}]" |
To combine objects, use the spread syntax shown above or Object.assign(). The reference lists an Object.merge() method, but in our test on 2.43.7 every call to it on an object failed with merge() is only callable on type "Array".
Dates and times
n8n expressions use the Luxon date library. $now is the current moment and $today is midnight at the start of the current day. Strings and Unix timestamps become dates with .toDateTime().
Dates use the workflow's timezone if one is set in its settings, otherwise the instance timezone (GENERIC_TIMEZONE when self-hosted). Our test instance ran in UTC.
Prefix each with $('Webhook').item.json.body.createdAt.toDateTime():
| Expression | Result |
|---|---|
.toISO() | "2026-10-14T09:30:00.000Z" |
.format('yyyy-MM-dd') | "2026-10-14" |
.toFormat('dd LLL yyyy, HH:mm') | "14 Oct 2026, 09:30" |
.weekdayLong | "Wednesday" |
.plus(7, 'days').toISO() | "2026-10-21T09:30:00.000Z" |
.plus({ hours: 2 }).toISO() | "2026-10-14T11:30:00.000Z" |
.minus(1, 'month').toISODate() | "2026-09-14" |
.startOf('month').toISO() | "2026-10-01T00:00:00.000Z" |
.endOf('week').toISO() | "2026-10-18T23:59:59.999Z" (weeks end on Sunday) |
.setZone('America/New_York').toISO() | "2026-10-14T05:30:00.000-04:00" |
.isBetween('2026-10-01', '2026-10-31') | true |
.diffTo('2026-12-25', 'days') | -71.60416666666667 |
.toMillis() | 1791970200000 |
More date expressions:
| Expression | Result |
|---|---|
$today.toISO() | "2026-10-18T00:00:00.000Z" on October 18 |
$today.plus(1, 'day').set({ hour: 9 }).toISO() | "2026-10-19T09:00:00.000Z", tomorrow at 9:00 |
(1760434200).toDateTime('s').toISO() | "2025-10-14T09:30:00.000Z" (Unix seconds; use 'ms' for milliseconds) |
'14/10/2026'.toDateTime('dd/MM/yyyy').toISODate() | "2026-10-14" |
DateTime.fromFormat('14/10/2026', 'dd/MM/yyyy').toISODate() | "2026-10-14" |
DateTime.fromISO('2026-10-14T09:30:00Z').toFormat('cccc') | "Wednesday" |
Three things that trip people up:
diffTo()is negative when the other date is later.createdAt.diffTo('2026-12-25', 'days')returned-71.6. Swap the dates, or wrap it inMath.abs(), to count days until a deadline.- Non-ISO strings need a format.
'14/10/2026'.toDateTime()failed with "cannot convert to Luxon DateTime." Pass the format:.toDateTime('dd/MM/yyyy'). - Format tokens are Luxon's.
yyyyis the year,MMthe month number,LLLthe short month name,ddthe day andHHthe 24-hour hour. The full list is in Luxon's formatting table.
Querying JSON with JMESPath
$jmespath(object, expression) runs a JMESPath query, which is handy for filtering nested arrays without writing a .filter().map() chain. Per the n8n docs, the object comes first and the query second:
{{ $jmespath($('Webhook').item.json.body.lines, '[?qty > `1`].sku') }}
// ["A1"]
{{ $jmespath($('Webhook').item.json.body, 'lines[*].price') }}
// [10, 29.9]Numbers in JMESPath filters go in backticks, as in `1` above.
Workflow, execution and environment data
| Expression | Result |
|---|---|
$workflow.name | "Expression test", the workflow's name |
$workflow.id | The workflow ID, also in the editor URL |
$execution.id | The current execution's ID, as a string |
$execution.mode | test for runs started from the editor, production for triggered runs and evaluation for workflow tests, per the docs |
$prevNode.name | "Prepare" |
$nodeVersion | 3.4 (the node's type version) |
$vars.<name> | A custom variable. Variables are available on self-hosted Business and Enterprise, and Pro and Enterprise on Cloud. |
$env.HOME | Failed with "access to env vars denied" |
Since n8n 2.0, access to environment variables from nodes is blocked by default (N8N_BLOCK_ENV_ACCESS_IN_NODE=true), according to the 2.0 breaking changes. n8n's environment variable table still lists the default as false, but our test confirmed the block. Keep secrets in credentials rather than reading them from the environment.
Expressions vs the Code node
The helper methods on this page only work in expressions. n8n's built-in methods page warns that the Code node "runs plain JavaScript against native Luxon, not n8n's expression engine." In practice:
- Methods marked Custom n8n functionality in the reference, such as
.toTitleCase(),.pluck()andDateTime.format(), aren't available in the Code node. Use plain JavaScript there, and Luxon's owntoFormat()instead offormat(). plus()andminus()take(7, 'days')in expressions but only{ days: 7 }in native Luxon.- Top-level helpers such as
$if()and$jmespath()are expression-only.
If you are moving logic between the two, our n8n 3.0 guide also lists the Code node helpers that 3.0 removes.
Common expression errors and fixes
"Referenced node doesn't exist"
The name inside $('...') doesn't match a node in the workflow. Check the spelling and capitalization against the canvas.
"Can't get data for expression" or "Referenced node is unexecuted"
The referenced node hasn't run yet in this execution. In the editor, run the workflow up to that node first. In a branch that may skip the node, check $('Node Name').isExecuted before reading its data.
"Multiple matching items for expression"
.item follows item linking back to the item that produced the current one. Nodes such as Summarize, Aggregate and Merge build one item from several, so n8n can't tell which one you mean. Use .first(), .last() or .all()[index], or reference a node before the one that combined the items.
"Info for expression missing from previous node"
A node in the chain didn't record which input item produced each output item. This usually means a Code node that returns new items without pairing information. n8n's docs explain how to preserve linking in the Code node.
Math returns text, like "49.901"
One of the values is a string. Convert it with .toNumber() first.
"merge() is only callable on type Array"
You called .merge() on an object. Use ({ ...a, ...b }) instead.
"cannot convert to Luxon DateTime"
The string isn't in a format .toDateTime() recognizes. Pass the format explicitly, for example .toDateTime('dd/MM/yyyy').
"access to env vars denied"
$env is blocked by default since n8n 2.0. Move the value into a credential, or, if you run your own instance and accept the risk, set N8N_BLOCK_ENV_ACCESS_IN_NODE=false. Our Docker Compose guide shows where n8n's environment variables go.
More on working with data in n8n: the expressions tag.
Sources
- Built-in methods and variables (n8n docs)
- Expression reference (n8n docs)
- Expressions for data transformation (n8n docs)
- Root variables and functions reference (n8n docs)
- JMESPath in n8n (n8n docs)
- Item linking errors (n8n docs)
- Custom variables (n8n docs)
- n8n 2.0 breaking changes, environment variable access (n8n docs)
Editorial team
The then8nblog editorial team researches every article from n8n's release notes, documentation and source code, states the n8n version each guide was tested on, and has every post reviewed by a human editor before it goes live.