data/skills/n8n-code-python/SKILL.md
Expert guidance for writing Python code in n8n Code nodes.
Recommendation: Use JavaScript for 95% of use cases. Only use Python when:
Why JavaScript is preferred:
this.helpers.httpRequest, etc.)# Basic template for Python Code nodes
items = _input.all()
# Process data
processed = []
for item in items:
processed.append({
"json": {
**item["json"],
"processed": True,
"timestamp": datetime.now().isoformat()
}
})
return processed
_input.all(), _input.first(), or _input.item[{"json": {...}}] format_json["body"] (not _json directly)Same as JavaScript - choose based on your use case:
Use this mode for: 95% of use cases
_input.all() or _items array (Native mode)# Example: Calculate total from all items
all_items = _input.all()
total = sum(item["json"].get("amount", 0) for item in all_items)
return [{
"json": {
"total": total,
"count": len(all_items),
"average": total / len(all_items) if all_items else 0
}
}]
Use this mode for: Specialized cases only
_input.item or _item (Native mode)# Example: Add processing timestamp to each item
item = _input.item
return [{
"json": {
**item["json"],
"processed": True,
"processed_at": datetime.now().isoformat()
}
}]
n8n offers two Python execution modes:
_input, _json, _node helper syntax_now, _today, _jmespath()from datetime import datetime# Python (Beta) example
items = _input.all()
now = _now # Built-in datetime object
return [{
"json": {
"count": len(items),
"timestamp": now.isoformat()
}
}]
_items, _item variables only_input, _now, etc.# Python (Native) example
processed = []
for item in _items:
processed.append({
"json": {
"id": item["json"].get("id"),
"processed": True
}
})
return processed
Recommendation: Use Python (Beta) for better n8n integration.
Access input data through underscore-prefixed variables. Each item is a dict shaped {"json": {...}}, so the actual fields live under ["json"].
# Pattern 1: _input.all() - Most common. Arrays, batch ops, aggregations
all_items = _input.all() # list of {"json": {...}} dicts
# Pattern 2: _input.first() - Very common. Single objects, API responses
data = _input.first()["json"] # built-in safety vs all_items[0]
# Pattern 3: _input.item - "Run Once for Each Item" mode ONLY
current = _input.item["json"] # None/error in All Items mode
# Pattern 4: _node - Reference a specific named node
webhook_data = _node["Webhook"]["json"]
http_data = _node["HTTP Request"]["json"]
See: DATA_ACCESS.md for the comprehensive guide — six _input.all() recipes (filter, transform, aggregate, sort, group, deduplicate), _input.first() and _input.item examples, multi-node combining, the JS-vs-Python variable table, and the decision tree.
MOST COMMON MISTAKE: Webhook data is nested under ["body"]
# ❌ WRONG - Will raise KeyError
name = _json["name"]
email = _json["email"]
# ✅ CORRECT - Webhook data is under ["body"]
name = _json["body"]["name"]
email = _json["body"]["email"]
# ✅ SAFER - Use .get() for safe access
webhook_data = _json.get("body", {})
name = webhook_data.get("name")
Why: Webhook node wraps all request data under body property. This includes POST data, query parameters, and JSON payloads.
See: DATA_ACCESS.md for full webhook structure details
CRITICAL RULE: Always return list of dictionaries with "json" key
# ✅ Single result
return [{
"json": {
"field1": value1,
"field2": value2
}
}]
# ✅ Multiple results
return [
{"json": {"id": 1, "data": "first"}},
{"json": {"id": 2, "data": "second"}}
]
# ✅ List comprehension
transformed = [
{"json": {"id": item["json"]["id"], "processed": True}}
for item in _input.all()
if item["json"].get("valid")
]
return transformed
# ✅ Empty result (when no data to return)
return []
# ✅ Conditional return
if should_process:
return [{"json": processed_data}]
else:
return []
# ❌ WRONG: Dictionary without list wrapper
return {
"json": {"field": value}
}
# ❌ WRONG: List without json wrapper
return [{"field": value}]
# ❌ WRONG: Plain string
return "processed"
# ❌ WRONG: Incomplete structure
return [{"data": value}] # Should be {"json": value}
Why it matters: Next nodes expect list format. Incorrect format causes workflow execution to fail.
See: ERROR_PATTERNS.md #2 for detailed error solutions
MOST IMPORTANT PYTHON LIMITATION: Cannot import external packages on default installs.
Self-hosted exception: external package availability depends entirely on the instance's Python runner configuration. If the user states their self-hosted instance has specific packages available in the Python runner environment, use them — don't refuse. When unsure, ask or write standard-library-only code.
❌ NOT available (raise ModuleNotFoundError): requests, pandas, numpy, scipy, bs4/BeautifulSoup, lxml.
✅ Available (standard library only): json, datetime, re, base64, hashlib, urllib.parse, math, random, statistics.
Need HTTP requests?
this.helpers.httpRequest() (the bare $helpers global is undefined in the task-runner sandbox)Need data analysis (pandas/numpy)?
Need web scraping (BeautifulSoup)?
See: STANDARD_LIBRARY.md for complete reference
Based on production workflows, the most useful Python patterns are:
restatistics moduleCopy-ready snippets for all five live in COMMON_PATTERNS.md, alongside 10 fully detailed production patterns (multi-source aggregation, markdown parsing, JSON comparison, CRM normalization, dictionary lookup, top-N filtering, and more).
import requests raises ModuleNotFoundError. Use the HTTP Request node or JavaScript instead.return [{"json": ...}].{"json": {...}} becomes [{"json": {...}}]..get(): _json.get("user", {}).get("name", "Unknown").["body"]: _json.get("body", {}).get("email", "no-email").See: ERROR_PATTERNS.md for the comprehensive guide — each error with wrong-vs-right code, error messages, nested-access fixes, an AttributeError bonus case, a prevention checklist, and a quick-fix table.
Most useful modules: json (parse/generate), datetime (dates + timedelta), re (regex), base64 (encode/decode), hashlib (hashing), urllib.parse (URL ops), and statistics (mean/median/stdev). Also available: math, random, collections, itertools, functools.
For a condensed cheat sheet plus full per-module examples, see STANDARD_LIBRARY.md.
# ✅ SAFE: Won't crash if field missing
value = item["json"].get("field", "default")
# ❌ RISKY: Crashes if field doesn't exist
value = item["json"]["field"]
# ✅ GOOD: Default to 0 if None
amount = item["json"].get("amount") or 0
# ✅ GOOD: Check for None explicitly
text = item["json"].get("text")
if text is None:
text = ""
# ✅ PYTHONIC: List comprehension
valid = [item for item in items if item["json"].get("active")]
# ❌ VERBOSE: Manual loop
valid = []
for item in items:
if item["json"].get("active"):
valid.append(item)
# ✅ CONSISTENT: Always list with "json" key
return [{"json": result}] # Single result
return results # Multiple results (already formatted)
return [] # No results
# Debug statements appear in browser console (F12)
items = _input.all()
print(f"Processing {len(items)} items")
print(f"First item: {items[0] if items else 'None'}")
The SplitInBatches node has two outputs:
main[0] = done — fires ONCE after all batches completemain[1] = each batch — fires for every batch (the loop body)Always add a Limit 1 node after the done output.
# ❌ WRONG
data = _node['HTTP Request']['json']
# ✅ CORRECT - call .first() then access json
data = _node['HTTP Request'].first()['json']
$getWorkflowStaticData('global') may not be available in Python Beta mode. If you need to accumulate data across SplitInBatches iterations, use a JavaScript Code node for the accumulation logic instead.
statistics module for statistical operationsthis.helpers.httpRequest())n8n Expression Syntax:
{{ }} syntax in other nodes{{ }})n8n MCP Tools Expert:
search_nodes({query: "code"})get_node({nodeType: "nodes-base.code"})validate_node({nodeType: "nodes-base.code", config: {...}})n8n Node Configuration:
n8n Workflow Patterns:
n8n Validation Expert:
n8n Code JavaScript:
Before deploying Python Code nodes, verify:
{"json": {...}}_input.all(), _input.first(), or _input.item.get() to avoid KeyError["body"] if from webhookReady to write Python in n8n Code nodes - but consider JavaScript first! Use Python for specific needs, reference the error patterns guide to avoid common mistakes, and leverage the standard library effectively.