Skip to content

/n8n-code-python

Write Python in n8n Code nodes (native Python, `language` pythonNative, n8n 2.x). Use when the user explicitly wants Python in a Code node, when migrating old Pyodide/"Python (Beta)" code that used _input/_json/_node/_now, or when a Python Code node fails with NameError,

From plugin
n8n-mcp-skills
6.6k15 skills3 hooks

Claude Code

$ npx -y skills add czlonkowski/n8n-skills --skill n8n-code-python --agent claude-code

SKILL.md

n8n-code-python.SKILL.md
name: n8n-code-python
description: Write Python in n8n Code nodes (native Python, `language` pythonNative, n8n 2.x). Use when the user explicitly wants Python in a Code node, when migrating old Pyodide/"Python (Beta)" code that used _input/_json/_node/_now, or when a Python Code node fails with NameError, "Security violations detected", "Import of standard library module … is disallowed", "__build_class__ not found", "A 'json' property isn't a dictionary", or "Python runner unavailable". Covers the only two variables (_items/_item), dict-only access, imports blocked by default, the sandbox's denied builtins, accepted return shapes, and how errors interact with onError. JavaScript is the default for Code nodes — native Python has no n8n helpers and, by default, no imports. EXCEPTION — for Python in the AI-agent-callable Custom Code Tool (@n8n/n8n-nodes-langchain.toolCode), use the n8n-code-tool skill instead (input is _query, return must be a string).

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**. `_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:

Security violations detected
Line 1: Import of standard library module 'json' is disallowed. Allowed stdlib modules: none
  • **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 rejecte

Read more
@czlonkowski
Ships withn8n-mcp-skills

Expert Claude Code skills for building flawless n8n workflows using the n8n-mcp MCP server

Stars on GitHub

Momentum

stars in total
6.6k
Star history of czlonkowski/n8n-skills on GitHub, a chart by star-history.com

Other skills on n8n-mcp-skills.

n8n-agents
Auto-invoked

n8n-agents

Design n8n AI agents the right way. Use when building or editing any @n8n/n8n-nodes-langchain.* AI node — an AI Agent, LLM chain,…

n8n-code-tool
Auto-invoked

n8n-code-tool

Write JavaScript or Python for the n8n Custom Code Tool (@n8n/n8n-nodes-langchain.toolCode) — the AI-agent-callable tool, NOT the…

More skills from other plugins.

agent-builder

agent-builder

Load immediately after an Agent intent, before the first agent_builder_* call. Governs how you create, configure, test, and…

n8n11d209k
@n8n-io@n8n-ioInstall Skill
browser-use

browser-use

Direct browser control via CDP for web interaction: automation, scraping, testing, screenshots, and site/app work. Also covers…

@browser-use@browser-useInstall Skill
agent-browser

agent-browser

Browser automation CLI for AI agents. Use when the user needs to interact with websites, including navigating pages, filling…

@vercel-labs-2@vercel-labs-2Install Skill
add-anydoc

add-anydoc

Add local office-document-to-Markdown conversion to NanoClaw agent containers with the pinned Firecrawl AnyDoc CLI. Use when…

@nanocoai@nanocoaiInstall Skill

Questions about n8n-code-python.

Which coding agents can use n8n-code-python?
n8n-code-python installs in Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot, OpenCode and Windsurf with the skills CLI, using each agent's flag shown on this page. Flowy's auto-invocation, which fires a skill when your prompt matches, runs in Claude Code only.