skills/n8n-code-python/SKILL.md
Use when writing Python in n8n Code nodes, using _input/_json/_node syntax, or choosing between Beta and Native Python modes. NEVER for JavaScript Code nodes (use n8n-code-javascript) or expression fields (use n8n-expression-syntax).
npx skillsauth add sharkitect-solutions/sharkitect-claude-toolkit n8n-code-pythonInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Choose JavaScript for 95% of n8n Code node work. Python only when you need:
statistics module (mean, median, stdev -- no JS equivalent in n8n)re (lookaheads, named groups, multi-pattern extraction)JavaScript advantages in n8n: $helpers.httpRequest(), Luxon datetime, broader community support.
If the task is pure data transformation, use JavaScript.
BROKEN (3 common mistakes in one):
import requests # CRASH: ModuleNotFoundError
data = _json["name"] # CRASH: KeyError (webhook nests under body)
return {"json": {"greeting": f"Hello {data}"}} # FAIL: not a list
WORKING:
name = _json.get("body", {}).get("name", "World")
return [{"json": {"greeting": f"Hello {name}"}}]
Why: No external imports (stdlib only), webhook data accessed via body key with safe .get() fallback, return is a list of dicts with "json" key.
n8n offers two Python execution modes. Pick one -- never mix their syntax.
Python (Beta) -- RECOMMENDED:
_input, _json, _node, _now, _today, _jmespath()Python (Native) (Beta):
_items (all-items mode), _item (each-item mode)_input, _node, _now, _today, _jmespath helpers| Operation | Beta mode | Native mode |
|-----------|-----------|-------------|
| Get all items | _input.all() | _items |
| Get first item | _input.first() | _items[0] |
| Current item (each-item) | _input.item | _item |
| Current item JSON | _json | _item["json"] |
| Cross-node reference | _node["Name"] | Not available |
| Current datetime | _now | datetime.now() |
| Current date | _today | datetime.today() |
| JMESPath query | _jmespath(data, expr) | Not available |
Every Code node must return a list of dicts, each with a "json" key.
# CORRECT - single result
return [{"json": {"total": 42, "status": "done"}}]
# CORRECT - multiple results
return [{"json": {"id": 1}}, {"json": {"id": 2}}]
# CORRECT - empty (no output items)
return []
# WRONG - dict without list wrapper (silent failure or crash)
return {"json": {"total": 42}}
# WRONG - missing "json" key (next node gets no data)
return [{"total": 42}]
Webhook node wraps POST/JSON payload under body. Headers, query params, method are siblings.
webhook = _input.first()["json"]
# Webhook output structure:
# {"headers": {...}, "params": {}, "query": {...}, "body": {YOUR DATA}, "method": "POST"}
# WRONG - KeyError, your fields are not at root
name = _json["name"]
# CORRECT
name = _json["body"]["name"]
# SAFE
name = _json.get("body", {}).get("name", "fallback")
Code nodes use direct Python variable access -- never template expressions.
# WRONG - expression syntax does not work in Code nodes
value = "{{ $json.field }}"
# CORRECT - direct Python access
value = _json["field"]
# All items from previous node (use in "Run Once for All Items" mode)
items = _input.all() # Returns list: [{"json": {...}}, ...]
# First item only
first = _input.first() # Returns dict: {"json": {...}}
# Current item (use in "Run Once for Each Item" mode ONLY)
current = _input.item # Returns dict: {"json": {...}}
# WARNING: _input.item is None in "All Items" mode -- will cause AttributeError
# Current item's JSON shorthand
data = _json # Same as _input.item["json"] or _input.first()["json"]
# Cross-node reference by node name
webhook_data = _node["Webhook"]["json"]
api_result = _node["HTTP Request"]["json"]
# Built-in datetime helpers
now = _now # datetime object for current time
today = _today # date object for current date
# JMESPath queries on data
result = _jmespath(_json, "body.users[?active==`true`].name")
# "Run Once for All Items" mode
for item in _items: # _items is the full list
value = item["json"]["field"]
# "Run Once for Each Item" mode
data = _item["json"] # _item is the current item
| JavaScript ($ prefix) | Python Beta (_ prefix) | Python Native |
|------------------------|------------------------|---------------|
| $input.all() | _input.all() | _items |
| $input.first() | _input.first() | _items[0] |
| $input.item | _input.item | _item |
| $json | _json | _item["json"] |
| $node["Name"] | _node["Name"] | Not available |
| $now | _now | N/A |
| $today | _today | N/A |
| $jmespath() | _jmespath() | N/A |
Key difference: Python uses underscore _ prefix. JavaScript uses dollar $ prefix.
Using $input in Python is a syntax error. Using _input in JavaScript is undefined.
n8n Python Code nodes have ONLY Python stdlib. No pip install. No venv.
Available stdlib modules: json, datetime, re, base64, hashlib, urllib.parse, urllib.request, math, random, statistics, collections, itertools, functools, copy, decimal, string, textwrap
NOT available (ModuleNotFoundError): requests, pandas, numpy, scipy, beautifulsoup4/bs4, lxml, selenium, psycopg2, pymongo, sqlalchemy, flask, fastapi, pillow, openpyxl
Workarounds for common needs:
| Need | Blocked library | n8n workaround |
|------|----------------|----------------|
| HTTP requests | requests | HTTP Request node before Code node, or use JavaScript $helpers.httpRequest() |
| Data analysis | pandas | List comprehensions + statistics module |
| Database queries | psycopg2, pymongo | Postgres/MySQL/MongoDB nodes before Code node |
| HTML parsing | beautifulsoup4 | HTML Extract node, or regex in Code node |
| Excel files | openpyxl | Spreadsheet File node |
urllib.request.urlopen() exists in stdlib but is limited (no easy headers, auth, POST body).
Prefer the HTTP Request node for any real API call.
| Rationalization | When It Appears | Why It's Wrong | |---|---|---| | "I'll use Python because I know it better" | Choosing language for Code node | n8n's JavaScript has httpRequest(), Luxon datetime, and better integration. Python should only be chosen for stdlib-specific needs (statistics, complex regex, hashlib) | | "I'll just import requests for the API call" | Need to fetch external data | requests is NOT available -- only stdlib. Use HTTP Request node before the Code node, or switch to JavaScript for $helpers.httpRequest() | | "The data is at _json['email']" | Processing webhook input | Webhook data is nested under body -- _json["body"]["email"] is correct. _json["email"] throws KeyError | | "I'll return the dict directly" | Writing return statement | Must return [{"json": {...}}] -- a list of dicts with "json" key. Returning {"json": {...}} (no list) silently fails or crashes | | "I'll use _input.item in All Items mode" | Processing multiple items | _input.item is None in "Run Once for All Items" mode -- it only works in "Run Once for Each Item" mode. Use _input.all() instead | | "I can mix Beta and Native syntax" | Confused about which mode | Beta uses _input/_json/_node. Native uses _items/_item. They are exclusive -- mixing causes NameError at runtime | | "I'll use expression syntax in the Code node" | Writing {{$json.field}} in Python | Template expressions only work in node parameter fields. In Code nodes, use direct Python: _json["field"] |
| Anti-Pattern | What It Looks Like | Why It Fails | Fix |
|---|---|---|---|
| Dollar-sign variables | $input.all(), $json["field"] | Python syntax error -- $ is not valid in Python identifiers | Use underscore prefix: _input.all(), _json["field"] |
| Bare dict return | return {"json": {"total": 42}} | n8n expects a list, not a dict -- next node receives nothing or crashes | Wrap in list: return [{"json": {"total": 42}}] |
| Direct webhook access | name = _json["name"] | KeyError -- webhook wraps payload under "body" | Use _json["body"]["name"] or .get("body", {}).get("name") |
| Expression in code | value = "{{ $json.field }}" | Literal string, not evaluated -- template syntax only works in node parameter fields | Use value = _json["field"] |
| External imports | import pandas as pd | ModuleNotFoundError at runtime -- only stdlib available | Use list comprehensions + statistics module, or pre-process with dedicated nodes |
| All-Items mode with .item | data = _input.item["json"] | AttributeError: NoneType -- _input.item is None in All Items mode | Use _input.all() to get list, then iterate |
NEVER import external libraries (requests, pandas, numpy, bs4) - only stdlib available
NEVER use $input/$json/$node (dollar sign) - Python uses _input/_json/_node (underscore)
NEVER return a plain dict - must return list: [{"json": {...}}] not {"json": {...}}
NEVER access webhook data at root - it is _json["body"]["field"], not _json["field"]
NEVER use {{}} expression syntax in Code nodes - use direct Python: _json["field"]
NEVER choose Python by default - JavaScript has better n8n integration (httpRequest, Luxon)
NEVER use _input/_node/_now/_today/_jmespath in Native mode - only _items/_item available
NEVER mix Beta and Native syntax - _input.all() (Beta) vs _items (Native) are exclusive
NEVER use _input.item in "All Items" mode - it is None and will cause AttributeError
items = _input.all()
total = sum(item["json"]["amount"] for item in items)
count = len(items)
avg = total / count if count > 0 else 0
return [{"json": {"total": total, "count": count, "average": avg}}]
from collections import defaultdict
items = _input.all()
groups = defaultdict(list)
for item in items:
key = item["json"]["category"]
groups[key].append(item["json"])
return [{"json": {"category": k, "items": v, "count": len(v)}} for k, v in groups.items()]
data = _json.get("body", _json) # Works for both webhook and non-webhook input
name = data.get("name", "Unknown")
email = data.get("email", "")
score = int(data.get("score", 0)) # Cast with default
return [{"json": {"name": name, "email": email, "score": score}}]
import hashlib
items = _input.all()
seen = set()
unique = []
for item in items:
key = item["json"]["email"].lower().strip()
h = hashlib.md5(key.encode()).hexdigest()
if h not in seen:
seen.add(h)
unique.append(item)
return unique # Already in [{"json": {...}}] format
Before writing Python in an n8n Code node, answer these:
["body"].import X where X is not stdlib will crash at runtime.development
When the user wants help with paid advertising campaigns on Google Ads, Meta (Facebook/Instagram), LinkedIn, Twitter/X, or other ad platforms. Also use when the user mentions 'PPC,' 'paid media,' 'ad copy,' 'ad creative,' 'ROAS,' 'CPA,' 'ad campaign,' 'retargeting,' or 'audience targeting.' This skill covers campaign strategy, ad creation, audience targeting, and optimization.
testing
--- name: using-sharkitect-methodology description: Use when starting any conversation in a Sharkitect workspace OR before any task involving NEW pricing, positioning, proposal, strategy, plan-execution, or schema-design work — mandates invocation of Sharkitect-specific methodology skills (pricing-strategy, marketing-strategy-pmm, smb-cfo, hq-revenue-ops, executing-plans, brainstorming) under the same anti-rationalization discipline as using-superpowers. Documentation has failed 4 times across H
testing
Use when user says 'end session', 'wrap up', 'stop for the day', 'done for today', 'close out', 'save session', 'wrapping up', or invokes /end-session. Runs the full 9-step end-of-session protocol: resource audit, MEMORY.md update, lessons capture, plan status, pending items, workspace checklist, .tmp/ audit, git commit+push, Supabase brain sync, session brief, summary. Final step schedules a detached self-kill of the current session ONLY (3s delay) so the window closes cleanly. Other claude.exe processes (active workspaces) are NOT touched -- orphan cleanup is handled separately by Claude-Orphan-Cleanup-Hourly with proper age safeguards. Do NOT use for: mid-session quick saves (use session-checkpoint), skill syncing (use sync-skills.py), brain memory queries (use supabase-sync.py pull), document freshness reviews (use document-lifecycle), resource gap detection (use resource-auditor).
testing
Remove signs of AI-generated writing from text. Use when editing or reviewing text to make it sound more natural and human-written. Based on Wikipedia's comprehensive "Signs of AI writing" guide. Detects and fixes patterns including: inflated symbolism, promotional language, superficial -ing analyses, vague attributions, em dash overuse, rule of three, AI vocabulary words, passive voice, negative parallelisms, and filler phrases.