How to Use the Code Node in n8n
The Code node lets you write JavaScript or Python inside n8n. It's the escape hatch for when built-in nodes can't do what you need.
That said, it's best practice to use this node as little as possible. Most things you can do with Edit Fields nodes, expressions, and the built-in nodes, and those tend to be better optimized, more stable, and give you clearer debugging than code. So the rule of thumb is: always check if there's an expression or a built-in node that does what you want. Only reach for Code when there isn't.
But when you need it, you really need it. Sometimes the alternative is chaining ten nodes together, a Sort, then a Filter, then an Edit Fields, then some expressions, and at some point it's just simpler to write a few lines of code that do all of it in one shot. Say you have a large Google Sheet and you need to match and pair rows based on some condition, group them, and output the result in a specific JSON structure for the next node. That's a Sort node, a Filter node, an Edit Fields node, maybe a Merge node, and a bunch of expressions. Or it's one Code node with a few lines of JavaScript.
When to Use Code vs Expressions vs Built-in Nodes
Here's the decision framework I actually use:
Simple field mapping (renaming, copying, setting static values) → Edit Fields (Set) node. Always.
Dynamic values, string manipulation, date formatting → Expressions. n8n's built-in expression methods are surprisingly powerful. Things like .toDateTime(), .extractDomain(), .capitalize() cover a lot of ground. When I have a data transformation problem, the first thing I do is ask Claude or ChatGPT: "is there a built-in n8n expression method for this?" Usually there is.
Complex logic across multiple items, custom sorting/pairing, anything the built-in nodes can't handle → Code node. Grouping items by a key, deduplicating based on multiple fields, reshaping nested JSON into a flat structure. Anything where the logic is more than a single transformation per field.
If you've done any programming, you'll probably reach for Code more often, because you know what JavaScript or Python can do in a few lines. That's fine. Just know that expressions and built-in nodes are more debuggable when things go wrong.
JavaScript vs Python
Both work. I use JavaScript more because I'm more familiar with it, but it doesn't really make a difference. Depending on the task, one language might have a better library for what you need. If there's some amazing Python library, just use the Python runner.
The syntax is slightly different:
JavaScript uses dollar-sign prefixes: $input.all(), $input.first(), $input.item, $json.
Python uses underscores and bracket notation: _input.all(), _input.item, _item["json"].
Python runs via Task Runners (a separate container in n8n v2). If you're self-hosting, your n8n-runners image version needs to match your n8n version exactly, and the broker must bind to 0.0.0.0, not 127.0.0.1. That second one trips everyone up.
For most tasks in this course, we'll use JavaScript. But everything conceptually applies to both.
The Two Modes
This was the most confusing thing when I first used the Code node: Run Once for All Items vs Run Once for Each Item.
Run Once for All Items is the default. Your code runs once. You access all input items with $input.all() and return an array of results. This is what you want 95% of the time.
Run Once for Each Item runs your code separately for every single item. You access the current item with $input.item. Use this when each item is completely independent and you don't need to compare or combine items.
The shortcut: if you need to sort, filter, group, or compare items with each other, use All Items mode. If each item stands alone, Each Item mode works. Not sure? Use All Items. You can always access individual items inside a loop.
Here's what each mode looks like:
All Items mode:
const items = $input.all();
const results = [];
for (const item of items) {
if (item.json.status === 'active') {
results.push({
json: {
name: item.json.name,
email: item.json.email
}
});
}
}
return results;
Each Item mode:
const data = $input.item.json;
return {
json: {
name: data.name.toUpperCase(),
processed: true
}
};
Notice that All Items mode returns an array (return results), while Each Item mode returns a single object (return { json: ... }).
The Return Format
Every Code node must return items in a specific format: an array of objects, each with a json key.
return [
{ json: { name: "Alice", score: 95 } },
{ json: { name: "Bob", score: 87 } }
];
The confusing part: n8n doesn't always fail when you get this wrong. Since version 0.166.0, it tries to auto-wrap simple returns. So you might return something slightly wrong and it works, then later hit a different case where it breaks. Be explicit. Always use the { json: { ... } } format.
Common errors you'll see:
"Please return an array of objects" — you returned something that isn't an array of objects. Usually it's a bare value or an array of primitives:
// WRONG: returning a string
return "hello";
// WRONG: returning an array of strings
return ["a", "b", "c"];
// CORRECT: array of objects with json key
return [{ json: { message: "hello" } }];
"A 'json' property isn't an object" — your json key is pointing to an array, a string, or null instead of an object:
// WRONG: json points to an array
return [{ json: [1, 2, 3] }];
// WRONG: json points to a string
return [{ json: "hello" }];
// CORRECT: json points to an object, arrays go inside
return [{ json: { numbers: [1, 2, 3] } }];
One thing that trips people up: you can return more items than you received. If you get one input item with an array inside it, you can iterate over that array and return ten output items. You're creating new items, not just modifying existing ones. That's the whole point of the [{ json: {} }, { json: {} }, ...] format.
// One input item with a list inside it
const data = $input.first().json;
// Return one output item per person
return data.people.map(person => ({
json: {
name: person.name,
department: person.dept
}
}));
Accessing Data
The most common patterns:
// All items from current input
$input.all()
// First item only
$input.first()
// Current item (Each Item mode only)
$input.item
// Shorthand for current item's JSON
$json
// Data from a specific earlier node
$('HTTP Request').first().json
// All items from an earlier node
$('Edit Fields').all()
// Environment variables (self-hosted only)
$env.API_KEY
The $('Node Name') syntax is how you grab data from any node upstream, not just the one directly connected. Useful when you need data from several steps back.
For webhook data, the payload is usually under $json.body. That trips up almost everyone the first time.
Debugging
Debugging Code node problems is harder than debugging built-in nodes.
Built-in nodes expect a series of common errors and give you nicely formatted messages telling you what went wrong. The Code node gives you raw console errors. If you're using an npm package, those errors can get especially cryptic. I once had a PDF parsing library break when n8n switched to task runners. Some obscure error about streaming buffers and binaries. Not fun to debug.
Your debugging toolkit:
The Execution Panel is your best friend. Click any node after running a workflow and you can inspect exactly what data went in and what came out. This is the first thing to check, always.
console.log() outputs to your browser console (F12 then Console tab), not to the n8n UI. Useful for quick checks, but you have to have dev tools open to see anything.
The Execution Panel is more practical. Click any node after a run and you can see its input and output data right in the n8n UI. If your Code node crashes, click on it, check the Input tab, and you can see exactly what data it received. Most of the time that's enough to figure out what went wrong.
Return debug values alongside your real output when your code runs but produces wrong results:
return [{ json: { debug: myVariable, result: actualData } }];
Since n8n displays the returned JSON in the output panel, you can inspect intermediate values without opening browser dev tools. Remove the debug key when it works.
The general principle: built-in nodes are easier to debug. If something is going wrong in your Code node and you're spending a lot of time on it, consider whether you could do the same thing with built-in nodes instead. Sometimes the "slower" approach with more nodes is actually faster because you can inspect every step.
Let AI Write It
I've pretty much stopped writing code in Code nodes myself. AI does it for me, and it does it really well.
If you've followed the course from the beginning, you've already read the guide on how to vibe code n8n workflows. The same approach works specifically for Code nodes, and it's arguably where AI helps the most. Describing a data transformation in plain English and getting working code back is faster than writing it yourself.
The key trick that makes AI vastly better at writing Code node code: pin data in your workflows. When you pin data on a node, that data gets included in the workflow JSON file. So when an AI agent reads your workflow, it can see exactly what data is being passed between nodes, what the JSON structure looks like, what fields exist. Without pinned data, the agent is guessing at your data shape. With it, it knows.
If you're using Claude Code with the n8n MCP server and n8n skills, it can read your workflow, see the pinned data, understand the JSON structure, and write Code node logic that actually fits. The difference is night and day.
Even if you're not familiar with coding, this is what makes the Code node accessible. You don't need to memorize the $input.all() syntax or the return format. You describe what you want, the AI writes it, and you verify the output in the Execution Panel.
Installing npm and pip Packages
This is the feature that makes n8n genuinely different from Zapier or Make. When you self-host n8n, you can install any npm or pip package and use it in your Code nodes. Need to parse a PDF? There's a package. Detect disposable email addresses? Package. Scrape HTML? cheerio. Process images? sharp.
This basically enables n8n to replace most coding workflows. If you can use any of the popular libraries, it's so much more powerful than any no-code tool. That's really a decisive feature.
Using a package in a Code node looks like this:
const cheerio = require('cheerio');
const html = $input.first().json.htmlContent;
const $ = cheerio.load(html);
const title = $('h1').text();
return [{ json: { title } }];
Note: you must use require(). ES6 import does not work in Code nodes.
Setting up npm packages requires a custom Dockerfile and an environment variable. We'll cover the full setup (Dockerfile, docker-compose, which packages are worth installing) in a separate guide.
The Gateway
For me, the Code node was really a gateway into programming. Before n8n, if I wanted to build a cron job that always does X when Z happens, I wouldn't have known where to start. What language? What framework? How do I deploy it? How do I keep it running?
n8n handles all of that. You just write the logic in a Code node and the workflow takes care of triggering, scheduling, error handling, and keeping it running. You focus on the code, not the infrastructure.
If you've never written code before, that's fine. Between built-in expressions (which handle most things) and AI that can write Code node logic for you, the Code node is more accessible than it looks. And if you do start writing code here, you're learning real JavaScript or Python, skills that transfer everywhere.