All skills
czlonkowski avatar

/n8n-code-python

@b604bea

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. Use this skill when the user specifically requests Python for an n8n Code node. Note — JavaScript is recommended for 95% of use cases — only use Python when the user explicitly prefers it or the task requires Python-specific standard library capabilities (regex, hashlib, statistics). EXCEPTION — for Python in the AI-agent-callable Custom Code Tool (@n8n/n8n-nodes-langchain.toolCode), use the n8n-code-tool skill instead (input is _query, return must be a string).

Use this Skill: https://skilld.dev/gh/czlonkowski/n8n-mcp/n8n-code-python

This session only. Nothing lands on disk.

DATA_ACCESS.md

≈3.8k tokens on demand. Your agent reads this file only when SKILL.md points to it.

Data Access Patterns - Python Code Node

Complete guide to accessing data in n8n Code nodes using Python.


Overview

In n8n Python Code nodes, you access data using underscore-prefixed variables: _input, _json, _node.

Data Access Priority (by common usage):

  1. _input.all() - Most common - Batch operations, aggregations
  2. _input.first() - Very common - Single item operations
  3. _input.item - Common - Each Item mode only
  4. _node["NodeName"]["json"] - Specific node references
  5. _json - Direct current item (use _input instead)

Python vs JavaScript:

JavaScript Python (Beta) Python (Native)
$input.all() _input.all() _items
$input.first() _input.first() _items[0]
$input.item _input.item _item
$json _json _item["json"]
$node["Name"] _node["Name"] Not available

Pattern 1: _input.all() - Process All Items

Usage: Most common pattern for batch processing

When to use:

  • Processing multiple records
  • Aggregating data (sum, count, average)
  • Filtering lists
  • Transforming datasets

Basic Usage

# Get all items from previous node
all_items = _input.all()

# all_items is a list of dictionaries like:
# [
#   {"json": {"id": 1, "name": "Alice"}},
#   {"json": {"id": 2, "name": "Bob"}}
# ]

print(f"Received {len(all_items)} items")

return all_items

Example 1: Filter Active Items

all_items = _input.all()

# Filter only active items
active_items = [
    item for item in all_items
    if item["json"].get("status") == "active"
]

return active_items

Example 2: Transform All Items

all_items = _input.all()

# Transform to new structure
transformed = []
for item in all_items:
    transformed.append({
        "json": {
            "id": item["json"].get("id"),
            "full_name": f"{item['json'].get('first_name', '')} {item['json'].get('last_name', '')}",
            "email": item["json"].get("email"),
            "processed_at": datetime.now().isoformat()
        }
    })

return transformed

Example 3: Aggregate Data

all_items = _input.all()

# Calculate total
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
    }
}]

Example 4: Sort and Limit

all_items = _input.all()

# Get top 5 by score
sorted_items = sorted(
    all_items,
    key=lambda item: item["json"].get("score", 0),
    reverse=True
)
top_five = sorted_items[:5]

return [{"json": item["json"]} for item in top_five]

Example 5: Group By Category

all_items = _input.all()

# Group items by category
grouped = {}
for item in all_items:
    category = item["json"].get("category", "Uncategorized")

    if category not in grouped:
        grouped[category] = []

    grouped[category].append(item["json"])

# Convert to list format
return [
    {
        "json": {
            "category": category,
            "items": items,
            "count": len(items)
        }
    }
    for category, items in grouped.items()
]

Example 6: Deduplicate by ID

all_items = _input.all()

# Remove duplicates by ID
seen = set()
unique = []

for item in all_items:
    item_id = item["json"].get("id")

    if item_id and item_id not in seen:
        seen.add(item_id)
        unique.append(item)

return unique

Pattern 2: _input.first() - Get First Item

Usage: Very common for single-item operations

When to use:

  • Previous node returns single object
  • Working with API responses
  • Getting initial/first data point

Basic Usage

# Get first item from previous node
first_item = _input.first()

# Access the JSON data
data = first_item["json"]

print(f"First item: {data}")

return [{"json": data}]

Example 1: Process Single API Response

# Get API response (typically single object)
response = _input.first()["json"]

# Extract what you need
return [{
    "json": {
        "user_id": response.get("data", {}).get("user", {}).get("id"),
        "user_name": response.get("data", {}).get("user", {}).get("name"),
        "status": response.get("status"),
        "fetched_at": datetime.now().isoformat()
    }
}]

Example 2: Transform Single Object

data = _input.first()["json"]

# Transform structure
return [{
    "json": {
        "id": data.get("id"),
        "contact": {
            "email": data.get("email"),
            "phone": data.get("phone")
        },
        "address": {
            "street": data.get("street"),
            "city": data.get("city"),
            "zip": data.get("zip")
        }
    }
}]

Example 3: Validate Single Item

item = _input.first()["json"]

# Validation logic
is_valid = bool(item.get("email") and "@" in item.get("email", ""))

return [{
    "json": {
        **item,
        "valid": is_valid,
        "validated_at": datetime.now().isoformat()
    }
}]

Example 4: Extract Nested Data

response = _input.first()["json"]

# Navigate nested structure
users = response.get("data", {}).get("users", [])

return [
    {
        "json": {
            "id": user.get("id"),
            "name": user.get("profile", {}).get("name", "Unknown"),
            "email": user.get("contact", {}).get("email", "no-email")
        }
    }
    for user in users
]

Pattern 3: _input.item - Current Item (Each Item Mode)

Usage: Common in "Run Once for Each Item" mode

When to use:

  • Mode is set to "Run Once for Each Item"
  • Need to process items independently
  • Per-item API calls or validations

IMPORTANT: Only use in "Each Item" mode. Will be undefined in "All Items" mode.

Basic Usage

# In "Run Once for Each Item" mode
current_item = _input.item
data = current_item["json"]

print(f"Processing item: {data.get('id')}")

return [{
    "json": {
        **data,
        "processed": True
    }
}]

Example 1: Add Processing Metadata

item = _input.item

return [{
    "json": {
        **item["json"],
        "processed": True,
        "processed_at": datetime.now().isoformat(),
        "processing_duration": random.random() * 1000  # Simulated
    }
}]

Example 2: Per-Item Validation

item = _input.item
data = item["json"]

# Validate this specific item
errors = []

if not data.get("email"):
    errors.append("Email required")
if not data.get("name"):
    errors.append("Name required")
if data.get("age") and data["age"] < 18:
    errors.append("Must be 18+")

return [{
    "json": {
        **data,
        "valid": len(errors) == 0,
        "errors": errors if errors else None
    }
}]

Example 3: Conditional Processing

item = _input.item
data = item["json"]

# Process based on item type
if data.get("type") == "premium":
    return [{
        "json": {
            **data,
            "discount": 0.20,
            "tier": "premium"
        }
    }]
else:
    return [{
        "json": {
            **data,
            "discount": 0.05,
            "tier": "standard"
        }
    }]

Pattern 4: _node - Reference Other Nodes

Usage: Less common, but powerful for specific scenarios

When to use:

  • Need data from specific named node
  • Combining data from multiple nodes

Basic Usage

# Get output from specific node
webhook_data = _node["Webhook"]["json"]
api_data = _node["HTTP Request"]["json"]

return [{
    "json": {
        "from_webhook": webhook_data,
        "from_api": api_data
    }
}]

Example 1: Combine Multiple Sources

# Reference multiple nodes
webhook = _node["Webhook"]["json"]
database = _node["Postgres"]["json"]
api = _node["HTTP Request"]["json"]

return [{
    "json": {
        "combined": {
            "webhook": webhook.get("body", {}),
            "db_records": len(database) if isinstance(database, list) else 1,
            "api_response": api.get("status")
        },
        "processed_at": datetime.now().isoformat()
    }
}]

Example 2: Compare Across Nodes

old_data = _node["Get Old Data"]["json"]
new_data = _node["Get New Data"]["json"]

# Simple comparison
changes = {
    "added": [n for n in new_data if n.get("id") not in [o.get("id") for o in old_data]],
    "removed": [o for o in old_data if o.get("id") not in [n.get("id") for n in new_data]]
}

return [{
    "json": {
        "changes": changes,
        "summary": {
            "added": len(changes["added"]),
            "removed": len(changes["removed"])
        }
    }
}]

Critical: Webhook Data Structure

MOST COMMON MISTAKE: Forgetting webhook data is nested under ["body"]

The Problem

Webhook node wraps all incoming data under a "body" property.

Structure

# Webhook node output structure:
{
    "headers": {
        "content-type": "application/json",
        "user-agent": "..."
    },
    "params": {},
    "query": {},
    "body": {
        # ← YOUR DATA IS HERE
        "name": "Alice",
        "email": "alice@example.com",
        "message": "Hello!"
    }
}

Wrong vs Right

# ❌ WRONG: Trying to access directly
name = _json["name"]  # KeyError!
email = _json["email"]  # KeyError!

# ✅ CORRECT: Access via ["body"]
name = _json["body"]["name"]  # "Alice"
email = _json["body"]["email"]  # "alice@example.com"

# ✅ SAFER: Use .get() for safe access
webhook_data = _json.get("body", {})
name = webhook_data.get("name")  # None if missing
email = webhook_data.get("email", "no-email")  # Default value

Example: Full Webhook Processing

# Get webhook data from previous node
webhook_output = _input.first()["json"]

# Access the actual payload
payload = webhook_output.get("body", {})

# Access headers if needed
content_type = webhook_output.get("headers", {}).get("content-type")

# Access query parameters if needed
api_key = webhook_output.get("query", {}).get("api_key")

# Process the actual data
return [{
    "json": {
        # Data from webhook body
        "user_name": payload.get("name"),
        "user_email": payload.get("email"),
        "message": payload.get("message"),

        # Metadata
        "received_at": datetime.now().isoformat(),
        "content_type": content_type,
        "authenticated": bool(api_key)
    }
}]

POST Data, Query Params, and Headers

webhook = _input.first()["json"]

return [{
    "json": {
        # POST body data
        "form_data": webhook.get("body", {}),

        # Query parameters (?key=value)
        "query_params": webhook.get("query", {}),

        # HTTP headers
        "user_agent": webhook.get("headers", {}).get("user-agent"),
        "content_type": webhook.get("headers", {}).get("content-type"),

        # Request metadata
        "method": webhook.get("method"),  # POST, GET, etc.
        "url": webhook.get("url")
    }
}]

Choosing the Right Pattern

Decision Tree

Do you need ALL items from previous node?
├─ YES → Use _input.all()
│
└─ NO → Do you need just the FIRST item?
    ├─ YES → Use _input.first()
    │
    └─ NO → Are you in "Each Item" mode?
        ├─ YES → Use _input.item
        │
        └─ NO → Do you need specific node data?
            ├─ YES → Use _node["NodeName"]
            └─ NO → Use _input.first() (default)

Quick Reference Table

Scenario Use This Example
Sum all amounts _input.all() sum(i["json"].get("amount", 0) for i in items)
Get API response _input.first() _input.first()["json"].get("data")
Process each independently _input.item _input.item["json"] (Each Item mode)
Combine two nodes _node["Name"] _node["API"]["json"]
Filter list _input.all() [i for i in items if i["json"].get("active")]
Transform single object _input.first() {**_input.first()["json"], "new": True}
Webhook data _input.first() _input.first()["json"]["body"]

Common Mistakes

Mistake 1: Using _json Without Context

# ❌ RISKY: _json is ambiguous
value = _json["field"]

# ✅ CLEAR: Be explicit
value = _input.first()["json"]["field"]

Mistake 2: Forgetting ["json"] Property

# ❌ WRONG: Trying to access fields on item dictionary
items = _input.all()
names = [item["name"] for item in items]  # KeyError!

# ✅ CORRECT: Access via ["json"]
names = [item["json"]["name"] for item in items]

Mistake 3: Using _input.item in All Items Mode

# ❌ WRONG: _input.item is None in "All Items" mode
data = _input.item["json"]  # AttributeError!

# ✅ CORRECT: Use appropriate method
data = _input.first()["json"]  # Or _input.all()

Mistake 4: Not Handling Empty Lists

# ❌ WRONG: Crashes if no items
first = _input.all()[0]["json"]  # IndexError!

# ✅ CORRECT: Check length first
items = _input.all()
if items:
    first = items[0]["json"]
else:
    return []

# ✅ ALSO CORRECT: Use _input.first()
first = _input.first()["json"]  # Built-in safety

Mistake 5: Direct Dictionary Access (KeyError)

# ❌ RISKY: Crashes if key missing
value = item["json"]["field"]  # KeyError!

# ✅ SAFE: Use .get()
value = item["json"].get("field", "default")

Advanced Patterns

Pattern: Safe Nested Access

# Deep nested access with .get()
value = (
    _input.first()["json"]
    .get("level1", {})
    .get("level2", {})
    .get("level3", "default")
)

Pattern: List Comprehension with Filtering

items = _input.all()

# Filter and transform in one step
result = [
    {
        "json": {
            "id": item["json"]["id"],
            "name": item["json"]["name"].upper()
        }
    }
    for item in items
    if item["json"].get("active") and item["json"].get("verified")
]

return result

Pattern: Dictionary Comprehension

items = _input.all()

# Create lookup dictionary
lookup = {
    item["json"]["id"]: item["json"]
    for item in items
    if "id" in item["json"]
}

return [{"json": lookup}]

Summary

Most Common Patterns:

  1. _input.all() - Process multiple items, batch operations
  2. _input.first() - Single item, API responses
  3. _input.item - Each Item mode processing

Critical Rule:

  • Webhook data is under ["body"] property

Best Practice:

  • Use .get() for dictionary access to avoid KeyError
  • Always check for empty lists
  • Be explicit: Use _input.first()["json"]["field"] instead of _json["field"]

See Also:

Source: SKILL.md on GitHub

1 warning9d3 checks · Risk SAFE
  • Gen Agent Trust Hub9d

    The skill provides comprehensive documentation and templates for writing Python code within n8n workflows. It correctly emphasizes the platform's security boundaries and limitations, such as the lack of external library support. No malicious behaviors were detected.

  • Socket9d

    No alerts

  • Snyk9d

    Risk: MEDIUM · 1 issue

Signed by skilld at b604bea. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 3 days ago.

Activeupdated 3 months ago

README badge

README badge for czlonkowski/n8n-mcp/n8n-code-python