n8n Code Python SkillSAFE
n8n skillset for Claude Code to build flawless n8n workflows
Overview
From the repository's own README, as read at the audited commit. Badges and raw HTML are left out.
Guidance for writing native Python in n8n Code nodes (language: "pythonNative", n8n 2.x).
Why this skill exists
n8n 2.0 removed the Pyodide-based "Python (Beta)" and replaced it with native Python running in a task runner. A large share of the Python n8n code in circulation (templates, forum answers, older docs, model training data) targets the old runtime and fails on the new one:
_input,_json,_node,_now,_today,_jmespath→NameError(only_items/_itemexist)item.json.field→AttributeError(dict access only)import json→Security violations detected(every import is blocked unless the instance allowlists it; n8n Cloud allows none)class Foo:→__build_class__ not found;type(),getattr(),hasattr()→NameError
The skill teaches the runtime as it actually behaves. Every rule and pattern was verified by execution on n8n 2.38.5, and the skill keeps pushing agents toward JavaScript, expressions, and native nodes whenever the user hasn't explicitly asked for Python.
Skill activation
- The user explicitly wants a Python Code node
- Migrating legacy Pyodide Python (
_input.all(),_json,_node[...]) - A Python Code node fails with
NameError,Security violations detected,Import of standard library module ... is disallowed,A 'json' property isn't a dictionary, orPython runner unavailable
Not for the AI-agent Custom Code Tool (toolCode) — that's n8n-code-tool.
Example queries:
- "Write a Python Code node that groups these items by country."
- "My Python Code node says name '_input' is not defined."
- "Why can't I import json in the Python Code node?"
File structure
SKILL.md
JavaScript-first rule; _items / _item and dict-only access; legacy→native migration table; imports blocked by default (Cloud vs self-hosted allowlist); sandbox limits (denied builtins, no classes, no dunders, nonlocal instead of global); verifie
97457683608eOBSERVED · 2026-10-23What it tells the agent
The instruction file, verbatim from the audited commit — this is the text the model reads, and the surface the audit's instruction layer examines. Quoted here so you can judge it without cloning anything.
---
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
```python
# 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")
]
```
```python
# 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 in expressions (Luxon) or JS. 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) | —Trust audit
SAFEgrade B · trust 89/100 Nothing in the source contradicts what it says it does. Grade A is reserved for packages that have also passed the behavioural sandbox.
| Layer | What it checks | Result |
|---|---|---|
| L0 | Provenance & inventory | PASS |
| L1 | Static analysis of the code | NA |
| L2 | Instruction surface (what it tells the agent) | PASS |
| L3 | Class-specific surface | PASS |
| L4 | Behavioural (sandbox) | SKIPPED |
What the source does
- Filesystem
- none-observed
- Network
- none-observed
- Shell
- none-observed
- Dependencies
- pinned
- Secrets in source
- none-found
Findings (0)
No findings outside the package's declared scope.
Gates applied: no_behavioural_pass.
97457683608efull audit observations/trust-audit/skill/czlonkowski__n8n-code-python.json · Report an issue / request a re-scanAudit history
Every audit this skill has had.
| Date | Source | Verdict | Grade | Score | Change |
|---|---|---|---|---|---|
| 2026-10-23 | 97457683608e | SAFE | B | 89 | first audit |
Questions
What does the n8n-code-python — czlonkowski/n8n-skills skill do?
n8n skillset for Claude Code to build flawless n8n workflows
Is n8n-code-python — czlonkowski/n8n-skills safe to install?
The audit found nothing in the source that contradicts what it says it does, and graded it B (89/100). Grade A is held back for packages that have also passed a sandboxed behavioural run, which is why a clean skill reads B.
What can n8n-code-python — czlonkowski/n8n-skills access on my machine?
The audit observed no filesystem, network or shell use at all in its source.
How current is this page?
The grade is for one exact copy of the source (97457683608e), read on 2026-10-23. The repository is watched, and a new audit runs when it changes — this is the first audit.
Add this badge to your README
Shows this page's audit result and links back to it. It updates when the audit does.
[](https://modelsatlas.com/skills/czlonkowski/n8n-code-python/)
Not your listing, or something out of date? Claim or update your listing.