Files
czlonkowski 4077036b23 feat: Complete Skill #7 - n8n Code Python
Implements comprehensive Python Code node guidance with critical focus on "NO external libraries" limitation.

## Skill #7 - n8n Code Python

**Critical Message**: Use JavaScript for 95% of use cases.

Python in n8n has NO external libraries (no requests, pandas, numpy).

### Files Created

**Core Skill Files (6 files, 4,205 lines total)**:

1. **SKILL.md** (748 lines)
   - When to use Python vs JavaScript (95% JavaScript recommendation)
   - Critical limitation: NO external libraries
   - Mode selection (All Items vs Each Item)
   - Data access overview (_input, _json, _node)
   - Return format requirements
   - Standard library overview

2. **DATA_ACCESS.md** (702 lines)
   - _input.all() - Process all items
   - _input.first() - Get first item
   - _input.item - Current item (Each Item mode only)
   - _node["Name"] - Reference other nodes
   - Webhook body structure (data under ["body"])
   - Pattern selection guide
   - Python vs JavaScript comparison

3. **STANDARD_LIBRARY.md** (974 lines)
   - Complete reference for available modules
   - json - JSON parsing and generation
   - datetime - Date/time operations
   - re - Regular expressions
   - base64 - Encoding/decoding
   - hashlib - Hashing (MD5, SHA256)
   - urllib.parse - URL operations
   - math, random, statistics
   - What's NOT available (requests, pandas, numpy, etc.)
   - Workarounds for missing libraries

4. **COMMON_PATTERNS.md** (794 lines)
   - 10 production-tested Python patterns
   - Multi-source data aggregation
   - Regex-based filtering
   - Markdown to structured data
   - JSON object comparison
   - CRM data transformation
   - Release notes processing
   - Array transformation
   - Dictionary lookup
   - Top N filtering
   - String aggregation
   - Python vs JavaScript pattern comparison

5. **ERROR_PATTERNS.md** (601 lines)
   - Top 5 Python-specific errors with solutions
   - Error #1: ModuleNotFoundError (THE critical Python error)
   - Error #2: Empty code / missing return
   - Error #3: KeyError (use .get() instead)
   - Error #4: IndexError (check bounds first)
   - Error #5: Incorrect return format
   - Error prevention checklist
   - Quick fix reference table
   - Testing patterns

6. **README.md** (386 lines)
   - Skill metadata and activation triggers
   - "JavaScript First" recommendation prominent
   - What this skill teaches
   - File structure overview
   - Integration with other skills
   - Success metrics checklist
   - Quick reference guide
   - Common use cases
   - Limitations and workarounds
   - Best practices

**Evaluations (5 scenarios)**:

1. **eval-001-module-import-error.json**
   - Tests understanding of external library limitation
   - Scenario: ModuleNotFoundError with requests
   - Expected: Recommend JavaScript or HTTP Request node

2. **eval-002-dictionary-keyerror.json**
   - Tests safe dictionary access with .get()
   - Scenario: KeyError when accessing missing field
   - Expected: Use .get() with default values

3. **eval-003-webhook-body-gotcha.json**
   - Tests webhook data under ["body"] understanding
   - Scenario: KeyError when accessing webhook data directly
   - Expected: Access via data.get("body", {})

4. **eval-004-return-format-error.json**
   - Tests proper return format requirement
   - Scenario: Returning plain dict instead of array
   - Expected: Return [{"json": {...}}]

5. **eval-005-standard-library-usage.json**
   - Tests knowledge of available modules
   - Scenario: What modules for JSON, hashing, dates, regex
   - Expected: json, hashlib, datetime, re (standard library only)

### Key Features

**Critical Limitations Emphasized**:
- NO external libraries (no requests, pandas, numpy)
- JavaScript recommended for 95% of use cases
- Only standard library available
- ModuleNotFoundError is #1 Python error

**Python-Specific Syntax**:
- Underscore prefix: _input, _json, _node (vs $ in JavaScript)
- Dictionary access: _json["body"]["field"] (vs dot notation)
- Safe access: .get() method with defaults

**Complete Standard Library Coverage**:
- 15+ modules documented with examples
- json, datetime, re, base64, hashlib, urllib.parse
- math, random, statistics, collections
- Clear list of what's NOT available
- Workarounds for missing functionality

**Production Patterns**:
- 10 tested patterns adapted from JavaScript
- Python-specific implementations
- List comprehensions and dictionary operations
- Standard library usage examples

**Error Prevention Focus**:
- Top 5 errors cover majority of failures
- ModuleNotFoundError prominently featured
- Safe dictionary access (.get())
- Proper return format emphasized
- Error prevention checklist

### Integration

Works seamlessly with:
- **n8n Code JavaScript**: Compare approaches, know when to use which
- **n8n Expression Syntax**: Different from {{}} expressions
- **n8n MCP Tools Expert**: Validate Code node configurations
- **n8n Workflow Patterns**: Code nodes in larger workflows
- **n8n Node Configuration**: Configure mode and connections

### Statistics

- **6 skill files**: 4,205 lines total
- **5 evaluations**: Cover critical Python scenarios
- **10 patterns**: Production-tested Python code
- **15+ modules**: Standard library coverage
- **5 top errors**: Prevention and solutions

### Design Principles

1. **JavaScript First**: 95% recommendation throughout
2. **Critical Limitation**: NO external libraries emphasized everywhere
3. **Safe Patterns**: .get() for dicts, bounds checking for lists
4. **Proper Format**: [{"json": {...}}] return format
5. **Standard Library**: Complete reference with examples
6. **Error Prevention**: Top 5 errors with solutions

### Recommendation

**Use JavaScript Code node for 95% of use cases.**

Use Python only when:
- Complex Python-specific logic required
- Python standard library features needed
- Team more comfortable with Python than JavaScript

For HTTP requests, date operations, and most transformations → Use JavaScript.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Conceived by Romuald Członkowski - https://www.aiadvisors.pl/en
2025-10-20 14:33:50 +02:00

18 KiB

name, description
name description
n8n-code-python 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 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

  1. Consider JavaScript first - Use Python only when necessary
  2. Access data: _input.all(), _input.first(), or _input.item
  3. CRITICAL: Must return [{"json": {...}}] format
  4. CRITICAL: Webhook data is under _json["body"] (not _json directly)
  5. CRITICAL LIMITATION: No external libraries (no requests, pandas, numpy)
  6. 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:

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:

  • 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:

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 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)
    }
}]

4. 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

5. 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

1. 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"]

2. 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 = ""

3. 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)

4. Return Consistent Structure

# ✅ CONSISTENT: Always list with "json" key
return [{"json": result}]  # Single result
return 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 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

n8n Documentation


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.