N8n Code Python
Romuald CzlonkowskiCommunityCreated by Romuald Czlonkowski
- Skill
- Tool integration
- Automation and integrations
- MIT
- No issues found
AutoGineer summary
Written by AutoGineer from the skill’s own files, not by the author
This skill provides essential guidelines for using the native Python runner in n8n 2.x Code nodes. Unlike the legacy Pyodide implementation, native Python runs in a restricted task runner environment without access to n8n-specific helpers like _input, _json, or _node. Users must adhere to strict sandbox limitations, including blocked imports by default, restricted built-in functions, and specific return shape requirements. This documentation helps developers migrate legacy code, troubleshoot common runtime errors, and optimize performance for Python-based data transformations within n8n workflows.
What it does
- Explains the use of _items and _item variables for data access in different execution modes.
- Details the security sandbox, including blocked imports and restricted built-in functions.
- Provides migration patterns for transitioning from legacy Pyodide code to native Python.
- Defines valid return shapes for both 'Run Once for All Items' and 'Run Once for Each Item' modes.
- Outlines performance considerations and error handling behaviors for node failures.
- Offers a checklist for verifying Python code before deployment.
When to use it
- When a workflow explicitly requires Python logic that cannot be achieved via standard n8n nodes or JavaScript.
- When migrating legacy n8n workflows that previously relied on Pyodide-based Python nodes.
- When troubleshooting NameError or Security violation errors in existing Python Code nodes.
- When you need to perform complex data manipulations that benefit from Python's native syntax.
Advantages
- Enables the use of native Python syntax, including comprehensions, generators, and lambdas.
- Provides clear guidance on avoiding silent data traps during error handling.
- Clarifies the distinction between native Python and JavaScript-based n8n helpers.
- Helps optimize workflow performance by recommending efficient execution modes.
Usage examples
- “How do I filter items in a native Python Code node?”
- Use 'Run Once for All Items' mode and return a list comprehension: return [it for it in _items if it['json'].get('active')].
- “Why am I getting a NameError when using _input in my Python node?”
- The _input helper is not available in native Python. Replace it with _items for 'Run Once for All Items' mode or _item for 'Run Once for Each Item' mode.
- “Can I import the json module in my Python Code node?”
- Imports are blocked by default. You must check if your n8n instance administrator has allowlisted the module in the task-runner configuration.
- “How do I return data from a Python Code node?”
- In 'Run Once for All Items' mode, return a list of dictionaries, e.g., [{'json': {'key': 'value'}}]. In 'Run Once for Each Item' mode, return a single dictionary.
Install
Download the skill as a ZIP:
https://autogineer.com/agent-skills/czlonkowski/n8n-code-python/downloadUnzip it into the cross-client skills folder of your project:
mkdir -p .agents/skills && unzip n8n-code-python.zip -d .agents/skills
Documentation by Romuald Czlonkowski
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
# Run Once for All Items (default mode)
return [
{"json": {"name": it["json"]["name"], "revenue": it["json"]["revenue"]}}
for it in _items
if it["json"].get("active")
]# Run Once for Each Item
row = _item["json"]
return {"json": {**row, "name_upper": (row.get("name") or "").upper()}}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.
_itemsin each-item mode (or_itemin all-items mode) raisesNameError.Dict access only.
it["json"]["name"]orit["json"].get("name").it.json.nameraisesAttributeError: '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/_itemkeeps 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"]raisesKeyError.
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:
Security violations detected
Line 1: Import of standard library module 'json' is disallowed. Allowed stdlib modules: nonen8n 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 allowjson,datetimeandre, 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,pandasandnumpyare 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_workflowcatch a few Python mistakes (import requests, missingreturn,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,_nowor_jmespath.[ ] No
importunless confirmed allowlisted on this instance.[ ] No classes,
type(),getattr/hasattror dunders.nonlocalinstead ofglobal.[ ] 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: 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.
Files
- ATTRIBUTION.mdAdded by AutoGineer887 B
- COMMON_PATTERNS.md8.2 KiB
- LICENSELicence file added by AutoGineer1.1 KiB
- README.md3.3 KiB
- SKILL.md10.8 KiB
ATTRIBUTION.md
Attribution
This folder is an unmodified copy of the Agent Skill "n8n-code-python", distributed by AutoGineer (https://autogineer.com/agent-skills).
Commit: e037563e101749e630e6688d121d04529fe6ee7b
Created by: Romuald Czlonkowski (https://github.com/czlonkowski)
Publisher: Romuald Czlonkowski (https://github.com/czlonkowski)
Licence: MIT License (https://github.com/czlonkowski/n8n-skills/blob/e037563e101749e630e6688d121d04529fe6ee7b/LICENSE)
Licence evidence: repository licence LICENSE
Retrieved: 2026-11-07
The skill's own files are unmodified copies. AutoGineer is not affiliated with or endorsed by the author or the publisher.
Files added by AutoGineer
ATTRIBUTION.md: this file, written by AutoGineer
LICENSE: copied from LICENSE
Requirements and compatibility
Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta
“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.
Needs: Needs Python, Needs Node.js
Security
No issues found by our static scan· No issues found
A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.
Attribution and licence
- Created by
- Romuald Czlonkowski
- Publisher
- Romuald Czlonkowski (Community)
- Source
- czlonkowski/n8n-skills · skills/n8n-code-python · commit e037563
- Licence
- MIT License (redistribution allowed)
- LICENSE (added by AutoGineer from LICENSE)
- ATTRIBUTION.md (written by AutoGineer)
- Synced
AutoGineer is not affiliated with or endorsed by Romuald Czlonkowski or Romuald Czlonkowski.
Download ZIP
The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download n8n-code-python.zip