Skill v1.0.1
currentAutomated scan100/1001 files
version: "1.0.1" name: n8n-code-python description: Write Python code in n8n Code nodes. Use when writing Python in n8n, using _input/_json/_node syntax, working with standard library, or need to understand Python limitations in n8n Code nodes.
Python Code Node (Beta)
Expert guidance for writing Python code in n8n Code nodes.
⚠️ Important: JavaScript First
Recommendation: Use JavaScript for 95% of use cases. Only use Python when:
- You need specific Python standard library functions
- You're significantly more comfortable with Python syntax
- You're doing data transformations better suited to Python
Why JavaScript is preferred:
- Full n8n helper functions ($helpers.httpRequest, etc.)
- Luxon DateTime library for advanced date/time operations
- No external library limitations
- Better n8n documentation and community support
Quick Start
# Basic template for Python Code nodesitems = _input.all()# Process dataprocessed = []for item in items:processed.append({"json": {**item["json"],"processed": True,"timestamp": datetime.now().isoformat()}})return processed
Essential Rules
- Consider JavaScript first - Use Python only when necessary
- Access data:
_input.all(),_input.first(), or_input.item - CRITICAL: Must return
[{"json": {...}}]format - CRITICAL: Webhook data is under
_json["body"](not_jsondirectly) - CRITICAL LIMITATION: No external libraries (no requests, pandas, numpy)
- Standard library only: json, datetime, re, base64, hashlib, urllib.parse, math, random, statistics
Mode Selection Guide
Same as JavaScript - choose based on your use case:
Run Once for All Items (Recommended - Default)
Use this mode for: 95% of use cases
- How it works: Code executes once regardless of input count
- Data access:
_input.all()or_itemsarray (Native mode) - Best for: Aggregation, filtering, batch processing, transformations
- Performance: Faster for multiple items (single execution)
# Example: Calculate total from all itemsall_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}}]
Run Once for Each Item
Use this mode for: Specialized cases only
- How it works: Code executes separately for each input item
- Data access:
_input.itemor_item(Native mode) - Best for: Item-specific logic, independent operations, per-item validation
- Performance: Slower for large datasets (multiple executions)
# Example: Add processing timestamp to each itemitem = _input.itemreturn [{"json": {**item["json"],"processed": True,"processed_at": datetime.now().isoformat()}}]
Python Modes: Beta vs Native
n8n offers two Python execution modes:
Python (Beta) - Recommended
- Use:
_input,_json,_nodehelper syntax - Best for: Most Python use cases
- Helpers available:
_now,_today,_jmespath() - Import:
from datetime import datetime
# Python (Beta) exampleitems = _input.all()now = _now # Built-in datetime objectreturn [{"json": {"count": len(items),"timestamp": now.isoformat()}}]
Python (Native) (Beta)
- Use:
_items,_itemvariables only - No helpers: No
_input,_now, etc. - More limited: Standard Python only
- Use when: Need pure Python without n8n helpers
# Python (Native) exampleprocessed = []for item in _items:processed.append({"json": {"id": item["json"].get("id"),"processed": True}})return processed
Recommendation: Use Python (Beta) for better n8n integration.
Data Access Patterns
Pattern 1: _input.all() - Most Common
Use when: Processing arrays, batch operations, aggregations
# Get all items from previous nodeall_items = _input.all()# Filter, transform as neededvalid = [item for item in all_items if item["json"].get("status") == "active"]processed = []for item in valid:processed.append({"json": {"id": item["json"]["id"],"name": item["json"]["name"]}})return processed
Pattern 2: _input.first() - Very Common
Use when: Working with single objects, API responses
# Get first item onlyfirst_item = _input.first()data = first_item["json"]return [{"json": {"result": process_data(data),"processed_at": datetime.now().isoformat()}}]
Pattern 3: _input.item - Each Item Mode Only
Use when: In "Run Once for Each Item" mode
# Current item in loop (Each Item mode only)current_item = _input.itemreturn [{"json": {**current_item["json"],"item_processed": True}}]
Pattern 4: _node - Reference Other Nodes
Use when: Need data from specific nodes in workflow
# Get output from specific nodewebhook_data = _node["Webhook"]["json"]http_data = _node["HTTP Request"]["json"]return [{"json": {"combined": {"webhook": webhook_data,"api": http_data}}}]
See: DATA_ACCESS.md for comprehensive guide
Critical: Webhook Data Structure
MOST COMMON MISTAKE: Webhook data is nested under ["body"]
# ❌ WRONG - Will raise KeyErrorname = _json["name"]email = _json["email"]# ✅ CORRECT - Webhook data is under ["body"]name = _json["body"]["name"]email = _json["body"]["email"]# ✅ SAFER - Use .get() for safe accesswebhook_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
Return Format Requirements
CRITICAL RULE: Always return list of dictionaries with "json" key
Correct Return Formats
# ✅ Single resultreturn [{"json": {"field1": value1,"field2": value2}}]# ✅ Multiple resultsreturn [{"json": {"id": 1, "data": "first"}},{"json": {"id": 2, "data": "second"}}]# ✅ List comprehensiontransformed = [{"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 returnif should_process:return [{"json": processed_data}]else:return []
Incorrect Return Formats
# ❌ WRONG: Dictionary without list wrapperreturn {"json": {"field": value}}# ❌ WRONG: List without json wrapperreturn [{"field": value}]# ❌ WRONG: Plain stringreturn "processed"# ❌ WRONG: Incomplete structurereturn [{"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
Critical Limitation: No External Libraries
MOST IMPORTANT PYTHON LIMITATION: Cannot import external packages
What's NOT Available
# ❌ NOT AVAILABLE - Will raise ModuleNotFoundErrorimport requests # ❌ Noimport pandas # ❌ Noimport numpy # ❌ Noimport scipy # ❌ Nofrom bs4 import BeautifulSoup # ❌ Noimport lxml # ❌ No
What IS Available (Standard Library)
# ✅ AVAILABLE - Standard library onlyimport json # ✅ JSON parsingimport datetime # ✅ Date/time operationsimport re # ✅ Regular expressionsimport base64 # ✅ Base64 encoding/decodingimport hashlib # ✅ Hashing functionsimport urllib.parse # ✅ URL parsingimport math # ✅ Math functionsimport random # ✅ Random numbersimport statistics # ✅ Statistical functions
Workarounds
Need HTTP requests?
- ✅ Use HTTP Request node before Code node
- ✅ Or switch to JavaScript and use
$helpers.httpRequest()
Need data analysis (pandas/numpy)?
- ✅ Use Python statistics module for basic stats
- ✅ Or switch to JavaScript for most operations
- ✅ Manual calculations with lists and dictionaries
Need web scraping (BeautifulSoup)?
- ✅ Use HTTP Request node + HTML Extract node
- ✅ Or switch to JavaScript with regex/string methods
See: STANDARD_LIBRARY.md for complete reference
Common Patterns Overview
Based on production workflows, here are the most useful Python patterns:
1. Data Transformation
Transform all items with list comprehensions
items = _input.all()return [{"json": {"id": item["json"].get("id"),"name": item["json"].get("name", "Unknown").upper(),"processed": True}}for item in items]
2. Filtering & Aggregation
Sum, filter, count with built-in functions
items = _input.all()total = sum(item["json"].get("amount", 0) for item in items)valid_items = [item for item in items if item["json"].get("amount", 0) > 0]return [{"json": {"total": total,"count": len(valid_items)}}]
3. String Processing with Regex
Extract patterns from text
import reitems = _input.all()email_pattern = r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b'all_emails = []for item in items:text = item["json"].get("text", "")emails = re.findall(email_pattern, text)all_emails.extend(emails)# Remove duplicatesunique_emails = list(set(all_emails))return [{"json": {"emails": unique_emails,"count": len(unique_emails)}}]
4. Data Validation
Validate and clean data
items = _input.all()validated = []for item in items:data = item["json"]errors = []# Validate fieldsif not data.get("email"):errors.append("Email required")if not data.get("name"):errors.append("Name required")validated.append({"json": {**data,"valid": len(errors) == 0,"errors": errors if errors else None}})return validated
5. Statistical Analysis
Calculate statistics with statistics module
from statistics import mean, median, stdevitems = _input.all()values = [item["json"].get("value", 0) for item in items if "value" in item["json"]]if values:return [{"json": {"mean": mean(values),"median": median(values),"stdev": stdev(values) if len(values) > 1 else 0,"min": min(values),"max": max(values),"count": len(values)}}]else:return [{"json": {"error": "No values found"}}]
See: COMMON_PATTERNS.md for 10 detailed Python patterns
Error Prevention - Top 5 Mistakes
#1: Importing External Libraries (Python-Specific!)
# ❌ WRONG: Trying to import external libraryimport requests # ModuleNotFoundError!# ✅ CORRECT: Use HTTP Request node or JavaScript# Add HTTP Request node before Code node# OR switch to JavaScript and use $helpers.httpRequest()
#2: Empty Code or Missing Return
# ❌ WRONG: No return statementitems = _input.all()# Processing...# Forgot to return!# ✅ CORRECT: Always return dataitems = _input.all()# Processing...return [{"json": item["json"]} for item in items]
#3: Incorrect Return Format
# ❌ WRONG: Returning dict instead of listreturn {"json": {"result": "success"}}# ✅ CORRECT: List wrapper requiredreturn [{"json": {"result": "success"}}]
#4: KeyError on Dictionary Access
# ❌ WRONG: Direct access crashes if missingname = _json["user"]["name"] # KeyError!# ✅ CORRECT: Use .get() for safe accessname = _json.get("user", {}).get("name", "Unknown")
#5: Webhook Body Nesting
# ❌ WRONG: Direct access to webhook dataemail = _json["email"] # KeyError!# ✅ CORRECT: Webhook data under ["body"]email = _json["body"]["email"]# ✅ BETTER: Safe access with .get()email = _json.get("body", {}).get("email", "no-email")
See: ERROR_PATTERNS.md for comprehensive error guide
Standard Library Reference
Most Useful Modules
# JSON operationsimport jsondata = json.loads(json_string)json_output = json.dumps({"key": "value"})# Date/timefrom datetime import datetime, timedeltanow = datetime.now()tomorrow = now + timedelta(days=1)formatted = now.strftime("%Y-%m-%d")# Regular expressionsimport rematches = re.findall(r'\d+', text)cleaned = re.sub(r'[^\w\s]', '', text)# Base64 encodingimport base64encoded = base64.b64encode(data).decode()decoded = base64.b64decode(encoded)# Hashingimport hashlibhash_value = hashlib.sha256(text.encode()).hexdigest()# URL parsingimport urllib.parseparams = urllib.parse.urlencode({"key": "value"})parsed = urllib.parse.urlparse(url)# Statisticsfrom statistics import mean, median, stdevaverage = mean([1, 2, 3, 4, 5])
See: STANDARD_LIBRARY.md for complete reference
Best Practices
1. Always Use .get() for Dictionary Access
# ✅ SAFE: Won't crash if field missingvalue = item["json"].get("field", "default")# ❌ RISKY: Crashes if field doesn't existvalue = item["json"]["field"]
2. Handle None/Null Values Explicitly
# ✅ GOOD: Default to 0 if Noneamount = item["json"].get("amount") or 0# ✅ GOOD: Check for None explicitlytext = item["json"].get("text")if text is None:text = ""
3. Use List Comprehensions for Filtering
# ✅ PYTHONIC: List comprehensionvalid = [item for item in items if item["json"].get("active")]# ❌ VERBOSE: Manual loopvalid = []for item in items:if item["json"].get("active"):valid.append(item)
4. Return Consistent Structure
# ✅ CONSISTENT: Always list with "json" keyreturn [{"json": result}] # Single resultreturn results # Multiple results (already formatted)return [] # No results
5. Debug with print() Statements
# 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'}")
When to Use Python vs JavaScript
Use Python When:
- ✅ You need
statisticsmodule for statistical operations - ✅ You're significantly more comfortable with Python syntax
- ✅ Your logic maps well to list comprehensions
- ✅ You need specific standard library functions
Use JavaScript When:
- ✅ You need HTTP requests ($helpers.httpRequest())
- ✅ You need advanced date/time (DateTime/Luxon)
- ✅ You want better n8n integration
- ✅ For 95% of use cases (recommended)
Consider Other Nodes When:
- ❌ Simple field mapping → Use Set node
- ❌ Basic filtering → Use Filter node
- ❌ Simple conditionals → Use IF or Switch node
- ❌ HTTP requests only → Use HTTP Request node
Integration with Other Skills
Works With:
n8n Expression Syntax:
- Expressions use
{{ }}syntax in other nodes - Code nodes use Python directly (no
{{ }}) - When to use expressions vs code
n8n MCP Tools Expert:
- How to find Code node:
search_nodes({query: "code"}) - Get configuration help:
get_node({nodeType: "nodes-base.code"}) - Validate code:
validate_node({nodeType: "nodes-base.code", config: {...}})
n8n Node Configuration:
- Mode selection (All Items vs Each Item)
- Language selection (Python vs JavaScript)
- Understanding property dependencies
n8n Workflow Patterns:
- Code nodes in transformation step
- When to use Python vs JavaScript in patterns
n8n Validation Expert:
- Validate Code node configuration
- Handle validation errors
- Auto-fix common issues
n8n Code JavaScript:
- When to use JavaScript instead
- Comparison of JavaScript vs Python features
- Migration from Python to JavaScript
Quick Reference Checklist
Before deploying Python Code nodes, verify:
- [ ] Considered JavaScript first - Using Python only when necessary
- [ ] Code is not empty - Must have meaningful logic
- [ ] Return statement exists - Must return list of dictionaries
- [ ] Proper return format - Each item:
{"json": {...}} - [ ] Data access correct - Using
_input.all(),_input.first(), or_input.item - [ ] No external imports - Only standard library (json, datetime, re, etc.)
- [ ] Safe dictionary access - Using
.get()to avoid KeyError - [ ] Webhook data - Access via
["body"]if from webhook - [ ] Mode selection - "All Items" for most cases
- [ ] Output consistent - All code paths return same structure
Additional Resources
Related Files
- DATA_ACCESS.md - Comprehensive Python data access patterns
- COMMON_PATTERNS.md - 10 Python patterns for n8n
- ERROR_PATTERNS.md - Top 5 errors and solutions
- STANDARD_LIBRARY.md - Complete standard library reference
n8n Documentation
- Code Node Guide: https://docs.n8n.io/code/code-node/
- Python in n8n: https://docs.n8n.io/code/builtin/python-modules/
Ready 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.