Real workflows need data from several steps back, dates and fallbacks, not just the previous node's fields.
$json only reaches the data of the node directly before. Often you need something from further back, such as the original trigger.
Referencing other nodes. $('Node name') reaches any earlier node by its name:
{{ $('Get customer').item.json.plan }}: the plan from the "Get customer" node, for the item that matches the current one. n8n tracks which output item came from which input item (called item linking or paired items), so.itemfollows that link..first()and.all()give the first item or all items of that node.
This is why clear node names matter: renaming a node updates expressions, but names like "HTTP Request3" make them unreadable.
Dates. $now is the current date and time, and $today is midnight today, both in the workflow's time zone. They come with formatting helpers from a date library called Luxon: {{ $now.toFormat('yyyy-MM-dd') }} or {{ $now.plus({ days: 7 }).toISODate() }}.
Helpers.
$if(condition, valueIfTrue, valueIfFalse): a quick either/or.$ifEmpty(value, fallback): use a fallback when a field is empty.$jmespath(): pick values out of deep JSON with a query language.
Context. $execution (the current run, including its ID), $workflow (name, ID) and $vars (shared variables, settings stored once and reused across workflows, on plans that include them).
If an expression grows past one line, a data node or the Code node is usually clearer.
Example
{{ $('Get customer').item.json.plan }}
{{ $now.plus({ days: 7 }).toFormat('yyyy-MM-dd') }}
{{ $ifEmpty($json.nickname, $json.firstName) }}
{{ $if($json.amount > 1000, 'manual review', 'auto-approve') }}Remember
- $('Node name').item.json.field reaches earlier nodes via item linking
- $now and $today with Luxon helpers handle dates
- $if, $ifEmpty and $vars cover common needs