# n8n Code node: pick the run mode, shape the return, respect the sandbox
Use this when a Code node returns nothing, returns nested/garbled data, throws
on `import`/`require`, or when Python code that worked on n8n 1.x breaks after
upgrading to 2.x. Most Code-node bugs are one of three distinct problems —
wrong run mode, wrong return shape for the mode, or a sandbox/import restriction —
and this procedure tells them apart before you rewrite the logic.
## 1. Choose the run mode FIRST: per-item vs all-items
The Code node has a **Mode** parameter with two values, and the semantics are
not subtle. Getting this wrong is the #1 cause of Code-node bugs.
- **Run Once for Each Item**: the code runs once per input item. Inside, `$json`
(JavaScript) refers to the current item only. Best for 1:1 per-item transforms
with independent logic.
- **Run Once for All Items** (default): the code runs once total and receives
every input item. Access them with `$input.all()`. Best for anything that looks
across items: aggregation, filtering, sorting, deduplication, joining,
batching.
Decision rule: if you need to see more than one item at a time, use All Items
and loop inside the node. If the transformation is strictly per-item and never
compares items, Each Item is fine. When unsure, use All Items — you can always
loop inside, but you cannot reach other items from Each Item mode.
Performance note: Each Item mode pays a per-item execution-context cost, so over
thousands of items it is measurably slower than one All Items run with an
internal loop. Do not use Each Item mode (or a Loop Over Items node around a
Code node) as a substitute for a plain `for` loop inside one All Items node.
## 2. Match the return shape to the mode
The mode also dictates what you must return. Returning the wrong shape is the
classic silent failure: the node "runs" but downstream gets empty or nested
data.
- **All Items mode**: return an **array** of item objects, each shaped
`{ json: {...}, binary: {...}, pairedItem: {...} }` (binary and pairedItem
only when needed).
- **Each Item mode**: return a **single** item object `{ json: {...} }` from
each invocation — n8n collects them into the output array for you. Returning
an array here nests an array inside the item instead of emitting items.
```javascript
// All Items: transform every item in one run
const items = $input.all();
return items.map((item, index) => ({
json: { ...item.json, total: item.json.price * item.json.qty },
pairedItem: { item: index }
}));
```
```javascript
// Each Item: one item in, one item out
return {
json: { ...$json, total: $json.price * $json.qty }
};
```
Item-linking (`pairedItem`) is what lets downstream expressions trace which input
item produced which output item; breaking it is a separate failure class from
getting the mode/shape wrong, so fix the mode and shape first.
## 3. JavaScript imports: built-ins are fine, externals are allowlisted
The JS Code node runs sandboxed. Built-in Node.js modules are available via
`require`. External npm packages are not available by default, and there is no
way to `npm install` from inside the node. To use one:
1. Install the package where the n8n process (or the task runner, with n8n 2.x
task runners enabled) can resolve it.
2. Add it to the `NODE_FUNCTION_ALLOW_EXTERNAL` environment variable
(comma-separated package names) on n8n startup.
Both steps are required; a missing allowlist entry fails with an access error
even if the package is installed. Related: `NODE_FUNCTION_ALLOW_BUILTIN`
controls which Node.js built-ins are importable.
## 4. Python: version decides what is even possible
Python support in the Code node changed completely between major versions — this
is the first thing to check when Python code breaks.
- **n8n 1.x**: Python runs on **Pyodide**, a WebAssembly port of CPython. Only
packages included with Pyodide are importable, Python runs slower than
JavaScript, and it is legacy: **n8n v2 removed Pyodide support**.
- **n8n 2.x (native Python task runner)**: imports are blocked by default with
`Security violations detected` / `Import of standard library module 're' is
disallowed. Allowed stdlib modules: none`. Imports are controlled by
allowlists, set as environment variables (comma-separated, `*` allows all):
- `N8N_RUNNERS_STDLIB_ALLOW` — Python standard library modules
- `N8N_RUNNERS_EXTERNAL_ALLOW` — third-party packages
- `N8N_RUNNERS_ALLOW_TRANSITIVE_IMPORTS` — boolean, default false; when true,
an allowlisted package's own internal imports skip the allowlist (needed for
packages with large dependency trees like pandas or boto3; the Code node's
own imports are still checked)
- Third-party packages must additionally be **installed into the runner image**:
extend the `n8nio/runners` image and install into
`/opt/runners/task-runner-python` (e.g. with `uv pip install`). An allowlist
entry alone fails if the package is not installed.
- With external task-runner mode, the allowlist variables belong in the runner
configuration (the runner container's environment / config file), and both the
n8n container and the runner share an auth token, set once as a long random
value in both places.
Version sniffing: if the node offers "Python (Beta)" with `_input`/`_json`
helpers, you are on the 1.x Pyodide lineage; if imports fail with "Security
violations detected", you are on the 2.x native runner and need the allowlists.
## 5. What the Code node cannot do
Regardless of version, the Code node cannot access the file system and cannot
make HTTP requests directly. Do not fight the sandbox:
- File reads/writes → the Read/Write Files from Disk node.
- HTTP calls → the HTTP Request node.
- Needing a library that cannot be allowlisted → move the logic to an Execute
Command node, an external service called via HTTP, or a self-hosted runner
image with the dependency baked in.
## 6. Checklist
1. Mode matches the logic: cross-item → All Items; 1:1 → Each Item.
2. Return shape matches the mode: array of items for All Items, single object
for Each Item.
3. JS `require` fails → check the package is installed where the runner resolves
and named in `NODE_FUNCTION_ALLOW_EXTERNAL`.
4. Python `import` fails on 2.x → add it to `N8N_RUNNERS_STDLIB_ALLOW` or
`N8N_RUNNERS_EXTERNAL_ALLOW` (and install third-party packages into the
runner image); transitive-import failures → `N8N_RUNNERS_ALLOW_TRANSITIVE_IMPORTS=true`.
5. Python worked on 1.x, broke on 2.x → Pyodide was removed; migrate to the
native runner allowlist model.
6. Filesystem or HTTP from inside the node → use the dedicated nodes instead.