## TL;DR

Install the package where the n8n process (or the task runner, with n8n 2.x task runners enabled) can resolve it. 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. Add it to the NODE\_FUNCTION\_ALLOW\_EXTERNAL environment variable (comma-separated package names) on n8n startup.

## When to use

You are seeing this: The Code node has a Mode parameter with two values, and the semantics are not subtle. Use this skill when you run into "n8n Code node: run modes, return shapes, and import restrictions".

## When not to use

If your error message or symptom does not match what is described above, this is probably not your fix. Search for your exact error text instead of forcing this one to fit.

## 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.
