n8n-code-python
Write Python code in n8n Code nodes. Use when writing Python in n8n, using _input/_json/_node syntax, working with standard library, or need to understand Python limitations in n8n Code nodes. Use this skill when the user specifically requests Python for an n8n Code node. Note — JavaScript is recomm
By czlonkowski · 4,172 installs
npx skills add czlonkowski/n8n-skills --skill n8n-code-python
Source repository · Upstream listing
Python Code Node (native)
Since n8n 2.0 the Code node's Python runs as native Python in a task runner ( language:
"pythonNative" ). The old Pyodide "Python (Beta)" is gone, and with it every n8n helper it had:
input , json , node , now , today and jmespath all raise NameError now. Code copied
from old templates, forum posts or older docs usually breaks on this.
JavaScript first — stronger than before
Native Python gives you two variables and plain Python, and by default no imports : no
json , datetime or re . Everything n8n specific ( $('Node') , $jmespath , Luxon,
this.helpers.httpRequest , static data) exists only in JavaScript. Use Python only when the user
explicitly asks for it. Even then, first check whether an expression, Edit Fields, or a native
node (Crypto, Date & Time, HTML, XML) does the job. See n8n code javascript and the transform
gatekeeper in n8n expression syntax .
Quick start
Node config: {"language": "pythonNative", "mode": "runOnceForAllItems" "runOnceForEachItem",
"pythonCode": "..."} on n8n nodes base.code typeVersion 2.
The only inputs: items and item
Mode Variable Shape
Run Once for All Items items list of plain dicts {"json": {...}, "pairedItem": {...}}
Run Once for Each Item item one plain dict {"json": {...}, "pairedItem": {...}}
Each variable exists only in its own mode . items in each item mode (or item in
all items mode) raises NameError .
Dict access only. it["json"]["name"] or it["json"].get("name") . it.json.name raises
AttributeError: 'dict' object has no attribute 'json' .
No other nodes. There is no node / $('Node') equivalent. If you need data from another
branch, bring it in with Merge first, or read it in JavaScript.
Webhook payloads are under ["body"] : items[0]["json"].get("body", {}).get("email") .
Binary data isn't covered here. Read and write binary in JavaScript (see n8n binary and data ).
pairedItem: returning items / item keeps it. When you build new dicts and downstream
uses $('Node').item , add "pairedItem": {"item": i} to each returned item. That isn't
verified on native Python yet, so test run before relying on it.
Missing keys: prefer .get(key, default) . row["missing"] raises KeyError .
Migration table for legacy code:
Legacy (Pyodide) Native
input.all() items
input.first()["json"] items[0]["json"] (guard if items )
input.item / json item / item["json"]
node["X"] not available: Merge upstream, or use JS
now , today not available: pass {{ $now.toISO() }} in via Edit Fields, or use JS
jmespath(data, q) not available: $jmespath in an expression, or a comprehension
item.json.field item["json"]["field"]
Imports: blocked by default
Every import (standard library and third party) is checked against an allowlist before the
code runs . The default allowlist is empty, so even import json rejects the whole node:
n8n Cloud: no imports at all.
Self hosted: the admin can allowlist modules in the task runner config (see
n8n self hosting → TASK RUNNERS.md ). Some instances therefore allow json , datetime
and re , and most don't.
Default to import free code. If an import would really help, confirm it first: a
one line test node import json + return [{"json": {"ok": True}}] , or ask the user. Never
assume.
Without imports: parse JSON strings upstream ( {{ JSON.parse($json.payload) }} in Edit Fields).
Do date math with the Date & Time node or Luxon in an expression. ISO 8601 strings still compare and sort correctly as
plain strings. Use the Crypto node for hashing.
requests , pandas and numpy are never available unless the admin built a custom runner
image. Use the HTTP Request node for HTTP.
Sandbox limits (these fail even without imports)
You write What happens Use instead
eval , exec , compile , open , input , type , getattr , setattr , hasattr , vars , dir , globals , locals , object , memoryview , breakpoint NameError: name 'type' is not defined (runtime) isinstance(x, dict) ; key in d / d.get(key)
class Foo: ... build class not found (runtime) dicts + functions
x. class , "{0. class }".format(x) , import ("json") Security violations detected (whole node rejected before running) —
global counter inside a function NameError: name 'counter' is not defined , because your code runs inside a wrapper function nonlocal counter
Everything else in plain Python works (verified): comprehensions, generators, lambdas, closures,
recursion, try / except , f strings / .format() / % , sorted / min / max / sum / any / all /
enumerate / zip / round , sets, isinstance , print() (output goes to the browser console).
Return shapes (verified)
Observed on n8n 2.38.5. The auto wrapping and passthrough behaviours below are undocumented and could
change in a later release. Re check with a test run after upgrading n8n.
Run Once for All Items
Return Result
[{"json": {...}}, ...] canonical, N items
[{...}, ...] (plain dicts) auto wrapped under json , N items
{"json": {...}} or a single plain dict 1 item
items (mutated in place) passthrough with your changes
None / no return error Cannot read properties of null (reading 'json')
Run Once for Each Item
Return Result
{"json": {...}} , a plain dict, or item 1 item
None the item is dropped (a built in filter)
a list error A 'json' property isn't a dictionary [item 0]
Value conversion on output: tuple becomes a list, set becomes the string "{1, 2}" ,
and a datetime becomes str(dt) ( "2026 09 16 10:18:00.025792" , not ISO). Convert explicitly
( sorted(s) , dt.isoformat() ).
Prefer the explicit [{"json": ...}] in all items mode and {"json": ...} in each item mode.
The auto wrapping works, but the explicit shape makes the intent obvious to the next reader.
Errors and onError
A plain raise ValueError("bad row") fails the node with that message. When the node has an
onError continue mode, the three failure kinds behave differently (verified):
Failure continueErrorOutput continueRegularOutput
Runtime exception ( raise , KeyError , NameError , denied builtin) {"error": "<message "} on the error output ( main[1] ) ✅ {"error": "<message "} on the main output
Static rejection ( Security violations detected : import, dunder) node marked failed, but the input items, unchanged, go out the success output ; main[1] stays empty input items, unchanged, on the main output
Bad return shape (list in each item mode, None in all items mode) same: unchanged input on the success output same
The last two are silent data traps: downstream nodes receive unprocessed input as if the code had
run, and the execution still shows success. No error branch catches them. Prevent them (no
imports unless confirmed, correct return shape) and confirm with a real test run, checking the
Code node's status and output (see n8n error handling ).
Other messages:
Python runner unavailable: Python 3 is missing from this system : the self hosted instance has
no Python task runner (the stock image ships none). It's an infrastructure problem, not a code
problem. See n8n self hosting → TASK RUNNERS.md .
validate node / validate workflow catch a few Python mistakes ( import requests , missing
return , return None ). They do not catch input / json , dot access, blocked stdlib
imports, dunder access or classes. A test execution is the only reliable check.
Performance
Each Python Code node costs roughly 0.4 s (about 1 s when the runner is cold), noticeably more
than a JS Code node or an expression. Process lists in Run Once for All Items mode rather than
per item, and don't chain several small Python nodes where one would do.
Checklist
[ ] The user actually asked for Python. Otherwise JS, an expression, or a native node.
[ ] Mode matches the variable: items (all items) / item (each item).
[ ] Only dict access. No input , json , node , now or jmespath .
[ ] No import unless confirmed allowlisted on this instance.
[ ] No classes, type() , getattr / hasattr or dunders. nonlocal instead of global .
[ ] Return shape fits the mode. Sets and datetimes converted explicitly.
[ ] Ran a real test execution and inspected the output items (validation alone won't catch the traps above).
Reference
[COMMON PATTERNS.md](COMMON PATTERNS.md) : 12 import free patterns, each verified on a live
n8n instance (filter, aggregate, group, dedupe, top N, flatten, validate, drop items, text
report, safe nested access, running totals, ISO timestamps).
Related skills
n8n code javascript : the default for Code nodes, with all n8n helpers.
n8n expression syntax : $jmespath , Luxon and the transform gatekeeper, often a better fit than any Code node.
n8n code tool : Python in the AI agent Custom Code Tool ( query , returns a string).
n8n error handling : wiring error outputs; the passthrough trap above.
n8n self hosting : enabling the Python task runner and allowlisting modules.