Chapter 20

Code node: a few lines of JavaScript inside a workflow

The ready-made nodes cover 90% of what you need. The odd 10% — complex data transformations, merge logic across several sources, content generated on the fly — is what n8n gives you an escape hatch for: the Code node. This chapter shows you how to plug that gap with a few lines of JavaScript inside a workflow. Beginner warning: this chapter contains JS. It is completely fine if you cannot read it — copy the patterns I give you and change the parameters. If you really cannot follow, jumping straight to Chapter 21 AI Agent is fine too; most business situations never need the Code node.

Why the Code node

After the first 19 chapters you will have noticed that eight cases out of ten are handled by a core node plus a SaaS integration node: pull data, filter it, send a message, write to a database. The Set node reshapes it, expressions fill values in dynamically, and Merge/Split put it back together.

But there is always that 10% the ready-made nodes cannot do:

  • Orders pulled out of a CRM that have to be regrouped along two dimensions, customer tier × product category — the Set node cannot do it, because that takes a loop.
  • An API response buried three levels deep in { result: { data: { items: [...] } } } when all you want is the items array — one expression can do it, but add a little more logic and it turns into a mess.
  • Shopify hands you 20 orders and you have to work out what percentage of total revenue the three largest ones account for — that takes sort, slice and reduce together.
  • Turning 100 items into the JSON structure Slack Block Kit expects — piles of nested { type, elements: [...] }, which would drive you mad in the Set node.

That is where the Code node comes in. It gives you a blank JavaScript editor: items go in, you write a few lines of code, items come out. You do not need to know how to build applications — for most situations, copying one of the four patterns below and changing the parameters is enough.

The AI HA agent workflow, 18 nodes, including a Code node
Figure 20-1 A real example: the AI HA agent workflow, 18 nodes, with a Code node in the middle doing the data shaping and logic the built-in nodes cannot.
Beginner warning: unlike the first 19 chapters, this one contains real JavaScript code. It is completely fine if you cannot read it — in practice, sales / marketing colleagues rarely have to write a Code node themselves, and recognizing that "there is a piece of code here doing X" is enough. If you really cannot follow, turn to Chapter 21 AI Agent and Chapter 17 error handling instead; nothing is affected.

Ask first: if Set can do it, do not use Code

The Code node looking cool is not a reason to overuse it. If Set / IF / Switch can solve it, do not open a Code node — because the Code node is JavaScript, and whoever maintains it later (or future you) has to read the code and know JS as well. That bar is a lot higher than a visual node.

What you want to doUse this firstWhen to move up to Code
Combine firstName and lastName into fullName The Set / Edit Fields node — one expression, {{ $json.firstName + ' ' + $json.lastName }}, and it is done; the visual route is fastest. No need to move up.
Amount > 1000 takes path A, everything else takes B The IF node — two branches drawn plainly on the canvas, so the logic reads at a glance. No need to move up.
Split into three paths by "order status" The Switch node — the native node for multi-way branching. No need to move up.
Filter items down to the ones where amount > 1000 The Filter node (newer versions have it), or an IF node with one output wired. Move up to Code when the condition crosses several fields, or when the condition itself has to be calculated.
Add a field or calculate a new value on every item The Set node plus an expression — that covers most of it. Move up to Code when you need a loop, or when the conditional transformation runs past 3-4 cases.
Sum all the items, average them, group them by something The Aggregate node (built in), or the Summarize node. Move up to Code for a top-N ranking, grouping on several dimensions, or anything statistically involved.
Take a nested API response apart and rebuild the JSON structure The Set node plus expressions usually holds up. Move up to Code only at three levels of nesting or more, or when you need map/reduce.
Tip: the test to apply — "could three Set nodes do this?" If they could, use Set; if not, open a Code node. Several Set nodes look like more work, but the next person who opens the workflow understands it at a glance, and that is worth far more than saving two nodes by burying a pile of code in one.

The Code node gives you two languages

Open a Code node and the Language option in the top right has two choices: JavaScript and Python. In practice, pick JavaScript 99% of the time. Here is why.

LanguageHow it runsUpsideDownsideRecommendation
JavaScript (the default) Runs in n8n's built-in sandbox; when self-hosted you can also switch to task runners (separate child processes, the newer architecture n8n recommends). The one n8n leads with; the helper API is complete; there are the most examples; it runs fast. The default sandbox cannot require external npm packages (you have to enable NODE_FUNCTION_ALLOW_EXTERNAL). Pick this by default. Without a specific reason, use JS.
Python From n8n 2 on it uses the native Python task runner (introduced in 1.111.0, stable in n8n 2); the earlier Pyodide (WebAssembly) route is marked legacy and n8n 2 no longer supports it. You have existing Python logic to move over; you can pip install the standard library plus third-party packages (only when you self-host and have the task runner set up). Native Python only takes bracket access (_input.item['json'], not .json); whether Cloud has it enabled depends on your plan; community examples are still mostly JS. Pick it only when you have existing Python logic to port. "I am better at Python" on its own is not a good reason — the community and the examples are all on the JS side.

Every remaining example in this chapter is JavaScript. The Python syntax differs like this: variables start with an underscore (_input, _json), and the native Python runner only takes bracket access (_input.item['json']['amount']) — the _input.item.json.amount dot syntax that old Pyodide allowed no longer works. When you need it, see the official docs at docs.n8n.io/build/code-in-n8n/using-the-code-node/.

Two run modes: all items at once vs one item at a time

Open a Code node and at the top of the Parameters panel on the right (next to the Language option) there is a Mode dropdown — the official docs word it "Choose a mode". The two options behave very differently, and picking the wrong one duplicates or drops data.

ModeBehaviorWhen to use itCommon use
Run Once for All Items (the default) The whole items array comes in at once, your code runs once, and it returns a new items array. When you have to see all the data before you can decide: aggregate, sort, group, rank the top N. "Total the revenue", "take the top 5 customers", "group by category", "merge two upstreams".
Run Once for Each Item Your code runs once for each item, as if a for loop had already been written for you. When a single item is enough to decide, and the transformation rule is the same for every one. "Add tax to each order", "reformat each message", "call an API per item to fill in data".
Warning: the mode you pick changes how the code is written:
  • All Items: use $input.all() to get everything; return has to return an array, [{ json: {} }, ...].
  • Each Item: use $input.item.json to get the current one; return returns a single { json: {} }.
Mixing them up throws Items to return were not valid or produces empty output. Examples in this chapter assume All Items mode unless stated otherwise.

Pattern 1: filter

Pick the items that match a condition out of a pile of them. All Items mode.

// Keep only orders over 1000
return $input.all().filter(item => item.json.amount > 1000);

In plain words:

  • $input.all(): gives you every item that came in (an array).
  • .filter(item => ...): a built-in JavaScript array method. It runs that test on every item and keeps only the ones where it is true.
  • item.json.amount > 1000: the test itself. Change this to your own condition and you are done.
  • return ...: hands the result to the downstream node.

Copy this, swap amount > 1000 for the condition you want (item.json.status === 'paid' and item.json.tags.includes('VIP') both work), and that is it. Chain several conditions with && (and) and || (or):

// Amount over 1000 and tier is gold
return $input.all().filter(item =>
  item.json.amount > 1000 && item.json.tier === 'gold'
);
Tip: the Filter node does this too, without any code. But when the condition crosses several fields, or the condition itself has to be calculated, the Code node is clearer.

Pattern 2: map (transform each item)

Transform every item: add a field, change a field, calculate a new value. All Items mode.

// Combine firstName and lastName into fullName; keep only fullName and age
return $input.all().map(item => ({
  json: {
    fullName: item.json.firstName + ' ' + item.json.lastName,
    age: item.json.age
  }
}));

In plain words:

  • .map(item => ({...})): runs that function on every item and returns a new item.
  • { json: {...} }: the format n8n requires for an item — every item has to be wrapped in a json layer. This one is easy to forget, and forgetting it throws Items to return were not valid.
  • fullName: ..., age: ...: the fields you want in the output. Write whichever fields you want to add.

If you only want to add a field to the original item rather than rebuild it, use the spread syntax ...item.json to keep every original field and then add the new ones:

// Keep every original field, then add taxAmount and total
return $input.all().map(item => ({
  json: {
    ...item.json,                      // spread the original fields
    taxAmount: item.json.amount * 0.05,
    total: item.json.amount * 1.05
  }
}));

The three dots in ...item.json are JavaScript's spread — "flatten every field of item.json in right here". Writing it this way saves you listing every field by hand.

Pattern 3: aggregate

Shrink a pile of items down to one total. All Items mode.

// Sum every order amount, then work out revenue and item count
const total = $input.all().reduce((sum, item) => sum + item.json.amount, 0);
const count = $input.all().length;

return [{
  json: {
    total: total,
    count: count,
    average: total / count
  }
}];

In plain words:

  • .reduce((sum, item) => sum + item.json.amount, 0): JavaScript's reduce, for adding an array up as it goes. sum starts at 0 (the second argument), each pass adds item.json.amount to it, and the total comes back at the end.
  • $input.all().length: the length of the array is the item count.
  • return [{ json: {...} }]: note that it still has to be wrapped in an array, even when you return only one item — [{ json: {} }]. Forget the outer [] and it blows up.

To group by something (totalling per customer, say), pair reduce with an object accumulator:

// Total the amounts per customer name
const groups = $input.all().reduce((acc, item) => {
  const key = item.json.customer;
  acc[key] = (acc[key] || 0) + item.json.amount;
  return acc;
}, {});

// turn it back into an items array and return it
return Object.entries(groups).map(([customer, total]) => ({
  json: { customer, total }
}));

What this does: sort every order into piles by customer, total each pile, and return one item per customer. This is the most common example of "Set cannot do it, but one block of Code can".

Pattern 4: call an external API from the Code node (rare)

Inside a Code node you can await a call to an external API directly, but the HTTP Request node is usually easier to maintain — it has a UI for headers, auth and retry. If you really do need the call inside Code:

// Call an external API and return the response as one item
const response = await this.helpers.httpRequest({
  method: 'GET',
  url: 'https://api.example.com/x',
  headers: { 'Accept': 'application/json' }
});

return [{ json: response }];

this.helpers.httpRequest(...) is a helper n8n builds in — the HTTP Request node, written in code. When to use it: when one stretch of logic has to call N APIs in a row, or when a field on an item decides whether to call at all. For a plain single API call, drop in an HTTP Request node.

Tip: the Code node sandbox has limits — not every Node.js API is available. this.helpers.httpRequest (and the other this.helpers.*, such as getBinaryDataBuffer), crypto (partly) and the built-in array, string and JSON methods all work; fs, external require and a bare fetch do not, by default. For npm packages, see the environment variable NODE_FUNCTION_ALLOW_EXTERNAL in the official docs (self-host only; not possible on Cloud).

Common helper APIs at a glance

The Code node comes with a pile of helpers n8n injects for you (the variables starting with $). 90% of the time you only ever use the few below.

How to write itWhat it isWhich mode it works in
$input.all() The whole array of items this node received. Mainly All Items mode; it works in Each Item too, but that is rare.
$input.item The current item (the full object, including json and binary). Only meaningful in Each Item mode.
$input.item.json The json part of the current item (what an expression calls $json). Common in Each Item mode.
$('Node Name').all() Grabs every item from a named upstream node (it does not have to be the direct upstream). Works in both modes.
$('Node Name').first() / .last() The first / last item from a named upstream node. Works in both modes.
$now The current time (a Luxon DateTime object). Works in both modes.
$today Today at 00:00 (Luxon). Works in both modes.
$workflow.id / $workflow.name The current workflow's id and name. Works in both modes.
$execution.id The id of this run (you can look it up on the Executions page). Works in both modes.
console.log(x) Prints to that node's log tab on the Executions page. Works in both modes (the debugging workhorse).
this.helpers.httpRequest({...}) Calls an external API (the equivalent of the HTTP Request node). Works in both modes; needs await.

For the full list see the official docs at docs.n8n.io/code/builtin/overview/, which also covers the more advanced ones: $jmespath() (JMESPath queries), $max() / $min(), and DateTime (calling the Luxon constructor directly).

What to do when the Code node fails

The Code node throws more errors than any other node type, because you are writing code. The good news is that n8n's error messages are usually clear enough — work through the 4 steps below.

  1. Read the red error message under the node panel

    When a Code node fails, the node itself turns red and an error message appears below it, usually including a line number. For example SyntaxError at line 3 — jump straight to line 3 and check for an unmatched quote or an unclosed bracket.

  2. Check that node's log on the Executions page

    Left sidebar Executions → click the failed run → click the Code node → switch to the Logs tab at the top. Every console.log(x) you wrote shows up here. Log as much as you like while you are still building; nobody will hold it against you.

  3. Check the output is in the right format

    The Code node ran fine but the downstream node gets nothing? Check whether the output is in the [{ json: {...} }] shape — an array on the outside, every item wrapped in json. Forgetting the json wrapper is the most common mistake: a bare return [{ name: 'Elmo' }] makes n8n read every item as empty. Correct: return [{ json: { name: 'Elmo' } }].

  4. Print nested objects with console.log(JSON.stringify(x, null, 2))

    A plain console.log(item) sometimes prints only [object Object] and shows you nothing. Use JSON.stringify(item, null, 2) to turn the object into readable JSON before printing, and even several layers of nesting are clear at a glance.

Concept: the debugging loop for a Code node is simple — edit the code → click Execute step → read the output / log → edit again. Do not write a long block before your first run; run it after two or three lines and you catch the broken line immediately. This is exactly how front-end JS is written.

Common pitfalls

  1. SyntaxError: Unexpected token or Unexpected identifier

    A JavaScript syntax error, usually unmatched quotes, an unclosed bracket, or a stray semicolon. The error message gives you the line number; go and look at that line and the ones around it. Common cases: mixing Chinese quotation marks “” with English '' (the first pair are not valid JS quotes); one half of a curly brace {} missing; => typed as ->.

  2. Cannot read properties of undefined (reading 'xxx')

    The field you are reaching for has nothing in its parent. For example item.json.customer.email, when the customer field does not exist. Fixes: (a) use optional chaining, item.json.customer?.email; (b) add an if test, if (!item.json.customer) return null;; (c) fix the upstream node first and confirm it really returns customer.

  3. The output is empty and the downstream node gets nothing

    Three common causes: (a) you forgot the return — a function with no return gives back undefined; (b) you forgot the { json: {} } wrapper — n8n does not recognize your output; (c) the filter condition is too strict — no item matches, so the result is an empty array. That is normal, but check whether you tightened the condition too far.

  4. Code runs very slowly (>30-second timeout)

    Usually a large array (>10,000 items) running .map / .filter. The fix: put a Split In Batches node upstream and work in batches, feeding 500-1000 items into the Code node at a time. Or check the code for a nested loop (a .map with a .filter inside it) — that is O(n²), and it blows up as soon as the data grows.

  5. Items to return were not valid

    What you returned is not in n8n's item format. The correct shape: [{ json: {...} }] — an array on the outside, and every element an object with a json key. Wrong: return { name: 'Elmo' } (not an array), return [{ name: 'Elmo' }] (no json wrapper). Each Item mode returns a single { json: {} }; All Items mode returns an array.

  6. ReferenceError: fetch is not defined or require is not defined

    The Community edition sandbox does not enable fetch or require. Call APIs with await this.helpers.httpRequest({...}) instead; to pull in an npm package, self-hosting is the only route — set the environment variable NODE_FUNCTION_ALLOW_EXTERNAL=lodash to open it up (not possible on Cloud).

FAQ

I do not know any JavaScript. Do I have to learn it?
No. Copy the four patterns this chapter teaches (filter / map / aggregate / http), change the conditions and field names to yours, and that is 80% of what you need. In practice, sales / marketing colleagues rarely have to write a Code node themselves — and when it does come up, hand the requirement to an engineer or an AI assistant along with an example of "the upstream output looks like this, I want output like this", and you get code in 10 minutes. It is completely fine if this chapter goes over your head; jumping straight to Chapter 21 AI Agent costs you nothing later on.
Is Python or JavaScript the better choice?
JavaScript first. Why: (a) n8n leads with JS and its helper API is the most complete; (b) over 90% of community examples are JS; (c) from n8n 2 on, Python runs through the native task runner (self-hosting means standing up a runner separately; the earlier Pyodide/WASM version is legacy / no longer supported in n8n 2); (d) native Python only takes bracket access (_input.item['json']['x']), so watch out when you rewrite a JS example. When to use Python: when you have existing Python logic (a string-handling function, say, or a Python snippet from an internal tool) to move over as-is, and you can pip install the packages it needs. "I am better at Python" on its own is not a good reason.
Can the Code node use npm packages (lodash or axios, say)?
Cloud and the default Community edition: no — the sandbox only opens the built-in modules (crypto, querystring and the like). Self-hosting: yes, but you have to set the environment variable NODE_FUNCTION_ALLOW_EXTERNAL=lodash,axios on the n8n container (list the package names you want to open, comma separated) and npm install them into the container. Cloud users who want a similar effect can split the logic into a sub-workflow (Chapter 19) or call an outside service with HTTP Request instead.
Can two Code nodes share a variable?
No. Every Code node is its own sandbox: a const x = 5 declared in one Code node is invisible in the next. To pass data between nodes, take n8n's regular route — put the data in the output's json, and read it in the next node with $('Previous Code').item.json.x. For a constant shared across the whole workflow, store it in a Set node or use workflow variables ($vars is Enterprise only).
Can a Code node await a call to another workflow?
It cannot call another workflow directly — that is the Execute Workflow node's job. What a Code node can await is this.helpers.httpRequest (calling an external API) and the other built-in helpers that return a Promise. If you want a sub-workflow to run from inside a Code node, restructure it: the Code node prepares the data → Execute Workflow node → the next Code node carries on. The division of labor is clearer that way too.
Can I paste Code node code that ChatGPT generated straight in?
Yes, but verify two things: (a) that the variables it used are real n8n helpers — a common failure is the AI writing items[0].json (the old v0 style) instead of $input.all()[0].json (the current one), which fails at run time with items is not defined; (b) whether the output is wrapped in the { json: {} } format. When you ask an AI for the code, tell it plainly: "n8n Code node, JavaScript, Run Once for All Items mode, use $input.all()" — the odds of getting it right go up a lot. Run the result on a small amount of data once before you take it live.
What is the difference between the Code node and an expression?
An expression is a dynamic fill-in-the-blank for a single field: one line only, no variable declarations, no loops, no await. The Code node is a whole node: many lines, variables, loops, await, console.log. The test to apply: if the logic fits inside one {{ }}, use an expression; if it does not (if/else with several branches, a for loop, an intermediate value to hold on to), use the Code node. Expressions are for filling in values dynamically; the Code node is for handling logic.
How do I know whether my code will run on Cloud?
The fastest method: run it on Cloud and see. Anything the sandbox blocks throws an error at run time (fetch is not defined, require is not defined, Module not found). As a rule: (a) all built-in JS syntax is fine (array methods, string methods, JSON, Math, Date); (b) n8n helpers ($input, $now, this.helpers.httpRequest) are fine; (c) npm packages, the fs file system and a bare fetch generally are not. If you are unsure, make a sandbox test workflow and run a few lines to feel it out.