Skip to content
MoveWorkflows

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. getWorkflowStaticData exists only in the JavaScript Code node (docs). Use Data tables for state.
  • Unsafe built-ins. N8N_RUNNERS_BUILTINS_DENY blocks eval, exec, compile, open, input, getattr, setattr, type, globals and 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