The n8n Code node: mastering expressions and JavaScript for data manipulation
Published 21 July 2026 · 6 min read
n8n gives you two ways to manipulate data: expressions, available in any field on any node, and the Code node, which runs full JavaScript. Mixing the two up leads either to unreadable expressions trying to do too much, or to a Code node pulled out for what should have been a simple date format. Knowing where to draw the line — and understanding the underlying items format — changes how fast you can build a workflow that works on the first try.
Expressions: one value, in one field
An n8n expression is written between double curly braces {{ }} directly inside a field — a Set node, a URL parameter, an HTTP header. It has access to the same global variables as the Code node: $json for the current item, $node["NodeName"].json (or the shorthand $('NodeName')) to reach a previous node by name, $now, $workflow, and so on.
{{ $json.email.toLowerCase() }}
{{ $node["Get Customer"].json.name }}
{{ $now.minus({ days: 7 }).toISO() }}
Expressions support a good chunk of JavaScript syntax (string methods, ternaries, array access), which sometimes tempts people into stacking them in a single field until it becomes an unreadable one-liner. The practical limit: the moment an expression needs an intermediate variable, a loop, or a multi-branch condition, that's the signal to switch to a Code node instead of nesting more ternaries.
This risk isn't unique to n8n: a landmark study by Raymond Panko, published in the Journal of Organizational and End User Computing in 1998, found that a high proportion of spreadsheets built by non-developer users contain undetected logic errors, precisely because a formula gets stacked and layered without the guardrails of a real programming language (Panko, 1998, Journal of Organizational and End User Computing). An n8n expression stretched across several nested ternaries reproduces exactly that risk: it's better to switch to a Code node the moment the logic goes beyond reading or lightly transforming a single value.
The items format: the structure every node works with
Between any two nodes, n8n always passes an array of items, each shaped like:
{
json: { /* your data */ },
binary: { /* optional files: PDFs, images... */ }
}
That's true even for a single item: a node receiving one order gets a one-element array, [{ json: { order_id: 123, ... } }]. This format constraint is the #1 source of errors in a poorly written Code node: forgetting the json envelope on output, or returning an object instead of an array.
The Code node's two modes
The Code node has a selector at the top of the panel: Run Once for All Items or Run Once for Each Item. The choice determines both how you access data and what shape you're expected to return.
Run Once for All Items
The code runs a single time, with access to every item via $input.all(). You must explicitly return an array of objects shaped like { json: {...} }. This is the mode for filtering, aggregating, deduplicating, or reordering a set of items — anything that needs to "see" several items at once.
// Filter orders over $100 and compute a running total
const items = $input.all();
const highValue = items.filter((item) => item.json.total > 100);
const grandTotal = highValue.reduce((sum, item) => sum + item.json.total, 0);
return highValue.map((item) => ({
json: { ...item.json, grandTotal }
}));
Run Once for Each Item
The code runs once per item, $json represents the current item directly, and the return is simpler — a single object, implicitly wrapped. This mode fits transformations that are independent from item to item: normalizing a field, computing a derived value, validating a format.
// Normalize an email and compute a simple score
const email = ($json.email || "").trim().toLowerCase();
const score = email.endsWith("@gmail.com") ? 1 : 2;
return { json: { ...$json, email, score } };
Looping over $input.all(): the most common pattern
The vast majority of "All Items" Code nodes follow the same skeleton: grab the items, transform with .map(), filter with .filter(), aggregate with .reduce().
const items = $input.all();
// Transform
const withDiscount = items.map((item) => ({
json: {
...item.json,
priceWithDiscount: item.json.price * 0.9
}
}));
// Filter
const inStock = withDiscount.filter((item) => item.json.stock > 0);
// Aggregate
const totalStock = inStock.reduce((sum, item) => sum + item.json.stock, 0);
return inStock;
These three methods cover most data-transformation needs without ever writing an explicit for loop — more readable, and less prone to indexing mistakes.
Reaching a previous node's data with $('Node Name')
$json only gives you the current item from the node immediately before. To reach further back in the workflow, $('Node Name').all() (or .first() for a single item) fetches items from a specific node by name, regardless of its position:
const customer = $('Get Customer').first().json;
const orders = $('Get Orders').all();
const enriched = orders.map((order) => ({
json: { ...order.json, customerName: customer.name }
}));
return enriched;
This is worth knowing the moment a workflow combines multiple sources — typically after a Merge node, or when cross-referencing context data fetched early in the workflow (like in our guide on connecting n8n to Supabase) with the result of a later call.
Handling errors in the Code node
An unguarded Code node crashes the entire execution on the first unexpected undefined. An explicit try/catch lets you decide what happens instead: log and continue, or return an error item usable downstream.
const items = $input.all();
const results = [];
for (const item of items) {
try {
const parsed = JSON.parse(item.json.rawPayload);
results.push({ json: { ...item.json, parsed, error: null } });
} catch (err) {
results.push({ json: { ...item.json, parsed: null, error: err.message } });
}
}
return results;
Capturing the error per item instead of letting one corrupted item take down the whole batch pairs well with a dedicated Error Workflow for more serious failures; see our article on error handling in n8n for the full picture.
Common pitfalls
- Unguarded undefined:
item.json.customer.emailthrows ifcustomeris missing. Prefer optional chaining (item.json.customer?.email) or an explicit default. - Type confusion: a value coming from a form or a webhook often arrives as a string even when it represents a number (
"42"instead of42); an explicitNumber()orparseInt()avoids comparisons that fail silently. - Mutating shared objects: directly modifying
item.json.field = valueinside a loop can produce unexpected side effects depending on the mode; prefer spreading ({ ...item.json, field: value }) to build a new object rather than mutating the existing one. - Forgetting the
jsonwrapper: returning{ name: "x" }instead of{ json: { name: "x" } }is the single most common beginner mistake on this node. - Mixing up the two modes mentally: writing code meant for "All Items" (
$input.all(), returning an array) while the selector is still set to "Each Item" — or the reverse — produces confusing format errors unless you check that setting first.
Wrapping up
Expressions cover reading and lightly transforming a single value in a field; the Code node takes over once you need to loop, aggregate, or combine multiple sources with real logic. Getting comfortable with the items format ({ json, binary }) and picking the right mode (All vs Each Item) eliminates most beginner mistakes. These JavaScript building blocks show up in nearly every workflow past the basics — including ones that split complex logic into reusable sub-workflows or build custom tools for an AI Agent, where the Code node is exactly what exposes a JavaScript function as a tool the model can call.
FAQ
Frequently asked questions
When should I use an expression instead of a Code node?
An expression is enough for reading or lightly transforming a single value in a field (concatenating a string, formatting a date, a simple calculation) without writing logic. Switch to a Code node as soon as you need to loop over multiple items with conditional logic, run calculations across an entire array, or combine data from several previous nodes with transformations beyond a one-line expression.
What's the difference between Run Once for All Items and Run Once for Each Item?
Run Once for All Items runs the code once with access to every item via $input.all(), and you must explicitly return an array of items as output — the mode to use for filtering, aggregating, or reordering a set. Run Once for Each Item runs the code separately for each item, with $json representing the current item directly, and implicitly returns one object per run — simpler for item-by-item transformations with no dependency between them.
Why does my Code node throw an error about the output format?
n8n expects an array of objects shaped like { json: {...} } as output, optionally with a binary key. Returning a plain object or an array of raw values directly ({ name: 'x' } instead of { json: { name: 'x' } }) triggers an error or unexpected behavior. Always check that every returned element is wrapped in that { json: ... } envelope.
How do I access data from a node further upstream, not just the direct predecessor?
Use $('Node Name').all() to fetch every item from a specific node by name, regardless of its position in the workflow, or $('Node Name').first() for just the first item. This works in both an expression and a Code node, and saves you from having to thread a value through every intermediate node just to reach it further down the line.
Bundle FlowKit Complet
€269