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.