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 nodes
items = _input.all()
Process data
processed = [] 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 _json directly)
-
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 _items array (Native mode)
-
Best for: Aggregation, filtering, batch processing, transformations
-
Performance: Faster for multiple items (single execution)
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 } }]
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.item or _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 item
item = _input.item
return [{ "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 , _node helper syntax
-
Best for: Most Python use cases
-
Helpers available: _now , _today , _jmespath()
-
Import: from datetime import datetime
Python (Beta) example
items = _input.all() now = _now # Built-in datetime object
return [{ "json": { "count": len(items), "timestamp": now.isoformat() } }]
Python (Native) (Beta)
-
Use: _items , _item variables only
-
No helpers: No _input , _now , etc.
-
More limited: Standard Python only
-
Use when: Need pure Python without n8n helpers
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.
Data Access Patterns
Pattern 1: _input.all() - Most Common
Use when: Processing arrays, batch operations, aggregations
Get all items from previous node
all_items = _input.all()
Filter, transform as needed
valid = [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 only
first_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.item
return [{ "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 node
webhook_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 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
Return Format Requirements
CRITICAL RULE: Always return list of dictionaries with "json" key
Correct Return Formats
✅ 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 []
Incorrect Return Formats
❌ 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
Critical Limitation: No External Libraries
MOST IMPORTANT PYTHON LIMITATION: Cannot import external packages
What's NOT Available
❌ NOT AVAILABLE - Will raise ModuleNotFoundError
import requests # ❌ No import pandas # ❌ No import numpy # ❌ No import scipy # ❌ No from bs4 import BeautifulSoup # ❌ No import lxml # ❌ No
What IS Available (Standard Library)
✅ AVAILABLE - Standard library only
import json # ✅ JSON parsing import datetime # ✅ Date/time operations import re # ✅ Regular expressions import base64 # ✅ Base64 encoding/decoding import hashlib # ✅ Hashing functions import urllib.parse # ✅ URL parsing import math # ✅ Math functions import random # ✅ Random numbers import 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:
- 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 ]
- 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) } }]
- String Processing with Regex
Extract patterns from text
import re
items = 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 duplicates
unique_emails = list(set(all_emails))
return [{ "json": { "emails": unique_emails, "count": len(unique_emails) } }]
- Data Validation
Validate and clean data
items = _input.all() validated = []
for item in items: data = item["json"] errors = []
# Validate fields
if 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
- Statistical Analysis
Calculate statistics with statistics module
from statistics import mean, median, stdev
items = _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 library
import 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 statement
items = _input.all()
Processing...
Forgot to return!
✅ CORRECT: Always return data
items = _input.all()
Processing...
return [{"json": item["json"]} for item in items]
#3: Incorrect Return Format
❌ WRONG: Returning dict instead of list
return {"json": {"result": "success"}}
✅ CORRECT: List wrapper required
return [{"json": {"result": "success"}}]
#4: KeyError on Dictionary Access
❌ WRONG: Direct access crashes if missing
name = _json["user"]["name"] # KeyError!
✅ CORRECT: Use .get() for safe access
name = _json.get("user", {}).get("name", "Unknown")
#5: Webhook Body Nesting
❌ WRONG: Direct access to webhook data
email = _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 operations
import json data = json.loads(json_string) json_output = json.dumps({"key": "value"})
Date/time
from datetime import datetime, timedelta now = datetime.now() tomorrow = now + timedelta(days=1) formatted = now.strftime("%Y-%m-%d")
Regular expressions
import re matches = re.findall(r'\d+', text) cleaned = re.sub(r'[^\w\s]', '', text)
Base64 encoding
import base64 encoded = base64.b64encode(data).decode() decoded = base64.b64decode(encoded)
Hashing
import hashlib hash_value = hashlib.sha256(text.encode()).hexdigest()
URL parsing
import urllib.parse params = urllib.parse.urlencode({"key": "value"}) parsed = urllib.parse.urlparse(url)
Statistics
from statistics import mean, median, stdev average = mean([1, 2, 3, 4, 5])
See: STANDARD_LIBRARY.md for complete reference
Best Practices
- Always Use .get() for Dictionary Access
✅ SAFE: Won't crash if field missing
value = item["json"].get("field", "default")
❌ RISKY: Crashes if field doesn't exist
value = item["json"]["field"]
- Handle None/Null Values Explicitly
✅ 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 = ""
- Use List Comprehensions for Filtering
✅ 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)
- Return Consistent Structure
✅ CONSISTENT: Always list with "json" key
return [{"json": result}] # Single result return results # Multiple results (already formatted) return [] # No results
- 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 statistics module 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_essentials("nodes-base.code")
-
Validate code: validate_node_operation()
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.