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.

View Source
name:n8n-code-pythondescription:"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."source:"https://github.com/czlonkowski/n8n-skills/tree/main/skills/n8n-code-python"risk:safe

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:

    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

    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.