Python in the n8n Code node
Updated 2026-10-21 · 5 min read
The n8n Code node runs Python as well as JavaScript. In n8n 2.x it runs native Python through task runners, a change from the Pyodide engine (CPython compiled to WebAssembly) that n8n 1.x used. Native Python is real CPython with a much smaller API surface inside n8n, and that difference trips up almost every script copied from older tutorials. This page covers what works, how to set it up on self-hosted n8n, and examples written for the current runtime.
Native Python vs the old Pyodide version
From the Code node docs and the 2.0 breaking changes:
| Pyodide (legacy, removed in 2.0) | Native Python (1.111.0+, stable in 2.x) | |
|---|---|---|
| Engine | CPython compiled to WebAssembly | CPython in a task runner |
| Input | _input.all(), _json, _('Node')… |
Only _items (all items) and _item (each item) |
| Field access | item.json.field worked |
Only item["json"]["field"] |
| Packages | Those bundled with Pyodide | Cloud: none. Self-hosted: allow-listed |
| Static data | No | No (JavaScript only) |
Tutorials that use _input.all() or dot access are written for Pyodide. On 2.x, rewrite them with _items and brackets. The reference-previous-nodes page still shows a Python tab with _input, which is legacy content.
The two modes
Run Once for All Items (default): _items is a list of items. Return a list of dicts.
out = []
for item in _items:
data = item["json"]
out.append({"json": {
"email": data["email"].lower(),
"domain": data["email"].split("@")[1],
}})
return out
Run Once for Each Item: _item is the current item. Return one dict.
data = _item["json"]
data["total_with_vat"] = round(data["total"] * 1.2, 2)
return _item
Every item has a json key holding a dict. Return items in that shape explicitly; the docs promise auto-wrapping only for JavaScript.
Examples
Aggregate
total = sum(float(i["json"].get("amount", 0)) for i in _items)
return [{"json": {"total": total, "count": len(_items)}}]
Filter
return [i for i in _items if i["json"].get("status") == "paid"]
Explode a list field
order = _items[0]["json"]
return [{"json": {"order_id": order["id"], **line}} for line in order["lines"]]
Dates without imports
On n8n Cloud you can't import even datetime, so pass dates in as ISO strings and compare them as strings, or compute dates in an expression before the Code node with {{ $now.toISO() }}. ISO-8601 strings in the same timezone sort correctly as text.
cutoff = _items[0]["json"]["cutoff"] # set by an Edit Fields node
return [i for i in _items if i["json"]["created_at"] >= cutoff]
What isn't available
- Other node data. Native Python has no
$('Node')equivalent. Pass what you need into the Code node's input with an Edit Fields or Merge node first. - HTTP requests and files. The Code node can't make HTTP requests or access the file system; use the HTTP Request node and Read/Write File From Disk.
- Credentials. Code nodes can't read credentials.
- Static data.
getWorkflowStaticDataexists only in the JavaScript Code node (docs). Use Data tables for state. - Unsafe built-ins.
N8N_RUNNERS_BUILTINS_DENYblockseval,exec,compile,open,input,getattr,setattr,type,globalsand others by default (task runner variables).
If a script needs any of these, the JavaScript Code node usually gets you further: it has $('Node'), $vars and static data.
Imports
| Where | Standard library | Third-party (pandas, numpy, requests) |
|---|---|---|
| n8n Cloud | Not allowed | Not allowed |
| Self-hosted, default runners image | Only what you allowlist in N8N_RUNNERS_STDLIB_ALLOW |
Only what's installed and listed in N8N_RUNNERS_EXTERNAL_ALLOW |
N8N_RUNNERS_ALLOW_TRANSITIVE_IMPORTS defaults to false, so a package that imports another package may need both allow-listed.
Self-hosted setup
Python Code nodes need task runners in external mode. The runner lives in a separate n8nio/runners container (launcher, JavaScript runner and Python runner), whose version must match n8nio/n8n (task runners). Since 2.0, the main image no longer contains the external-mode runner.
To add packages, extend the runners image (version 1.121.0 or later):
FROM n8nio/runners:<same version as n8nio/n8n>
USER root
RUN cd /opt/runners/task-runner-python && uv pip install numpy pandas
COPY n8n-task-runners.json /etc/n8n-task-runners.json
USER runner
and allow-list them in n8n-task-runners.json:
{
"task-runners": [
{
"runner-type": "python",
"env-overrides": {
"PYTHONPATH": "/opt/runners/task-runner-python",
"N8N_RUNNERS_STDLIB_ALLOW": "json,datetime,re",
"N8N_RUNNERS_EXTERNAL_ALLOW": "numpy,pandas"
}
}
]
}
The full Compose file with the runners sidecar is in n8n Docker setup.
Debugging
print() output goes to the browser console while you test in the editor, like console.log() in JavaScript. Pin the input data on the node so you can rerun it without calling upstream APIs.
Common errors:
| Error | Cause |
|---|---|
NameError: _input |
Pyodide-era code; use _items / _item |
AttributeError: 'dict' object has no attribute 'json' |
Dot access; use brackets |
| Import blocked | Module not allow-listed (or you're on Cloud) |
| Python option fails on self-hosted | No external-mode runners sidecar |
Python or JavaScript?
Pick Python when the logic is pure data manipulation and your team reads Python more easily. Pick JavaScript when you need other nodes' data, $vars, static data, or modules on n8n Cloud (crypto, moment). Many teams keep Python only for self-hosted instances with pandas installed.
From Pipedream Python steps
Pipedream ran Python 3.12 with def handler(pd), read data from pd.steps, and installed any package automatically on import (Pipedream Python). That model doesn't carry over: pd.steps becomes the node input, imports are restricted, and HTTP calls move to HTTP Request nodes. The migration mapping is in Pipedream Python steps to n8n.
Our converter wraps Python steps in a Python Code node with a compatibility layer and flags each import, so you can see which steps need self-hosted runners and which need a rewrite. Test every converted Python step on your runner: the default deny list of built-ins is strict, and a step that fails there needs a rewrite in plain functions. Check one step with the code step checker, or a whole export with the converter. The report and 1 workflow are free. Full export: $3 per workflow, $19 minimum — coming soon.
Not affiliated with Pipedream or n8n.
FAQ
- Can n8n run Python code?
- Yes. The Code node runs Python natively through task runners, available from n8n 1.111.0 and stable in n8n 2. The older Pyodide-based Python was removed in 2.0.
- How do I read input items in the n8n Python Code node?
- Use _items in Run Once for All Items mode and _item in Run Once for Each Item mode. Native Python doesn't support other built-ins such as _input or _json.
- Can I import pandas or requests in n8n Python?
- Not on n8n Cloud, where the Python Code node can't import any library, standard or third-party. On self-hosted n8n, add packages to the n8nio/runners image and allowlist them.
- Why do I get an error with item.json.field in Python?
- Native Python only accepts bracket access: item["json"]["field"]. Dot access worked only in the legacy Pyodide version.
- Does Python work in the default n8n Docker image?
- Python Code nodes need task runners in external mode, which means the n8nio/runners sidecar container. Since 2.0 the main n8nio/n8n image doesn't include the external-mode runner.
Related
Sources
- https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.code
- https://docs.n8n.io/deploy/host-n8n/configure-n8n/set-up-task-runners
- https://docs.n8n.io/deploy/host-n8n/configure-n8n/basic-configuration/use-environment-variables/task-runners
- https://docs.n8n.io/changelog/v20-breaking-changes
- https://docs.n8n.io/build/code-in-n8n/cookbook/built-in-methods-and-variables-examples/getworkflowstaticdata
- https://pipedream.com/docs/workflows/building-workflows/code/python