FlowKit

Python in n8n's Code node: what works, what doesn't, and the alternatives

Published 27 July 2026 · 7 min read

n8n's Code node is best known for its JavaScript, but a discreet selector at the top of the panel offers a second option: Python (Beta). For data teams and developers who think in list comprehensions rather than .map(), the temptation to write all transformation logic in Python is strong. It's possible — but within a tighter frame than it looks, because this Python doesn't run like a regular script on the server. This guide covers what actually works, the concrete limits of the implementation, and the serious alternatives when Python is genuinely required.

How n8n runs Python: Pyodide, not a system interpreter

When you select Python in the Code node, n8n doesn't call a python3 binary installed on the machine. It runs your code through Pyodide, a distribution of CPython compiled to WebAssembly, executing inside the n8n process itself. This architecture has two major consequences:

  • No server-side dependencies to install. Python works identically on n8n Cloud and self-hosted, with nothing for the administrator to configure.
  • A sandboxed environment. The code has no direct access to the network, the server's filesystem, or packages installed via pip on the machine. You're inside a WebAssembly bubble, not a shell.

It is real CPython — the syntax, the standard library (json, re, datetime, collections, hashlib…) and the language semantics are all there. But it's not the full Python environment a data engineer is used to.

The data-access syntax: underscore instead of dollar

In JavaScript, the Code node exposes $input, $json, $('Node Name'). In Python, $ isn't a valid character in an identifier, so n8n swaps the prefix for an underscore. The mapping is direct:

JavaScript Python
$input.all() _input.all()
$json _json
$('Get Customer') _('Get Customer')
$input.first() _input.first()

The item format doesn't change: the node receives and must return a list of dictionaries with a json key (and optionally binary). Forgetting that envelope remains mistake number one, in Python just as in JavaScript:

# Run Once for All Items — minimal structure
items = _input.all()

results = []
for item in items:
    results.append({
        "json": {
            "email": item.json.get("email", "").strip().lower()
        }
    })

return results

The two execution modes — Run Once for All Items and Run Once for Each Item — work as they do in JavaScript: the first sees every item at once via _input.all(), the second runs per item with _json representing the current one.

Three concrete examples

Transforming items with a list comprehension

The map/filter pattern reads very naturally in Python:

items = _input.all()

# Keep only orders > $100 and compute a discounted price
return [
    {
        "json": {
            **item.json,
            "price_discounted": round(item.json["total"] * 0.9, 2)
        }
    }
    for item in items
    if item.json.get("total", 0) > 100
]

It's the direct equivalent of .filter().map() in JavaScript — a matter of taste and team habit, not capability.

Deduplicating on a key

A classic after combining several sources with a Merge node: removing duplicates on the email field while keeping the first occurrence.

items = _input.all()
seen = set()
unique = []

for item in items:
    key = item.json.get("email", "").strip().lower()
    if key and key not in seen:
        seen.add(key)
        unique.append({"json": item.json})

return unique

Parsing inconsistent dates

The standard library's datetime module is available, which makes date cleanup more pleasant than in vanilla JavaScript:

from datetime import datetime

FORMATS = ["%d/%m/%Y", "%Y-%m-%d", "%d %b %Y"]

def parse_date(raw):
    for fmt in FORMATS:
        try:
            return datetime.strptime(raw.strip(), fmt).date().isoformat()
        except (ValueError, AttributeError):
            continue
    return None

return [
    {"json": {**item.json, "date_normalized": parse_date(item.json.get("date"))}}
    for item in _input.all()
]

This kind of normalization is exactly what you'd place before an Excel or CSV export node, so every row shares the same date format.

The real limits to know before writing everything in Python

No network or file access. No requests, no open() on a server file, no sockets. Any external call has to go through a dedicated node — HTTP Request with its pagination handling, integration nodes, and so on. The Python Code node is for transforming data, not fetching it.

Only the stdlib and Pyodide-compatible packages. The standard library is there, along with the pure-Python (or WebAssembly-precompiled) packages Pyodide can load. But the list actually available in the Code node depends on the Pyodide version your n8n release embeds: don't build a critical workflow on the assumption that pandas or numpy will be importable — test the import on your own instance before committing, and have a plan B.

Slower startup than JavaScript. The Pyodide runtime has to initialize, whereas JavaScript executes natively in Node.js. On a one-off execution it's imperceptible; on a workflow triggered hundreds of times per hour or a loop processing large batches, the overhead accumulates and shows up in execution times.

Beta status. n8n states it explicitly: the Python flavor of the Code node is in Beta, and some helper functions available on the JavaScript side have no Python equivalent. When something behaves strangely, the first debugging reflex is to reproduce the logic in JavaScript to isolate whether the problem lies in your code or in the runtime.

When to prefer JavaScript (spoiler: by default)

n8n recommends JavaScript as the Code node's default language, and it's advice worth following: native execution, instant startup, better coverage of built-in helpers, and nearly all community examples are written in JS.

Should you therefore force a Python team to write JavaScript? Not necessarily. A classic study by Lutz Prechelt, An Empirical Comparison of Seven Programming Languages (IEEE Computer, 2000 — see it on Google Scholar), compared the same programs written in seven languages and reached a now-famous conclusion: the variance in productivity and quality between developers far exceeds the variance between languages. In other words, the dominant factor isn't the language — it's how well the person writing it masters it. For a Code node doing pure data transformation, pick the language your team reads and debugs fastest — both flavors get the job done.

So the real dividing line isn't "JS or Python" but "Code node or not": as soon as the logic requires libraries absent from Pyodide, network, or file access, neither flavor is enough.

When Python is truly required: the two serious alternatives

A FastAPI or Flask microservice called via HTTP Request

The cleanest architecture: your Python code runs in its own service (with pandas, scikit-learn, pdfplumber — whatever you need), exposed over HTTP, and n8n calls it like any other API.

# minimal FastAPI microservice
from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()

class Payload(BaseModel):
    items: list[dict]

@app.post("/transform")
def transform(payload: Payload):
    # here: pandas, ML, the entire Python ecosystem
    return {"items": [enrich(i) for i in payload.items]}

On the n8n side, an HTTP Request node POSTs the items and collects the result. Remember to configure timeout and retry on that call, and to protect the endpoint with a token stored in an n8n credential rather than hardcoded. This is the approach you'll find in advanced document pipelines: the RAG Assistant Pack ($119) follows exactly this separation principle — n8n orchestrates, while heavy processing (embeddings, extraction) is delegated to specialized services called over HTTP.

The Execute Command node on a self-hosted instance

On a self-hosted instance where Python is installed, the Execute Command node can run a script directly on the machine: python3 /scripts/transform.py. It's simple and requires no extra service, but comes with non-negotiable precautions:

  • the node is unavailable on n8n Cloud, and runs commands with the n8n process's privileges — never inject a value coming from a webhook without strict validation (command injection risk);
  • Python dependencies must be installed and versioned on the host (or in the Docker image), which recreates the environment management the Code node was sparing you;
  • wrap the call with an Error Workflow: a script exiting with an error code must not fail silently.

Decision table: JS, Python-Pyodide, or microservice

Criterion JavaScript (Code node) Python-Pyodide (Code node) Python microservice
Simple data transformation ✅ Recommended default ✅ Fine if the team is Python-first ❌ Overkill
Performance / startup Native, fast Slower (Pyodide init) Network latency, but scalable
pandas, numpy, ML ⚠️ Not guaranteed, test first ✅ Full ecosystem
Network / file access ❌ (by design) ❌ (by design)
Works on n8n Cloud ✅ (service hosted separately)
Maintenance None None A service to deploy and monitor
Maturity in n8n Stable Beta Up to you

In summary

The Code node's Python is real Python, perfect for item transformation when your team is more at home there than in JavaScript: the _input.all() / _json syntax mirrors the JS one with an underscore in place of the dollar sign, and the stdlib covers parsing, deduplication, and normalization effortlessly. But Pyodide imposes its rules — no network, no files, no guarantee on scientific packages, slower startup — and the Beta status argues for keeping JavaScript as the default for anything trivial. The day your logic demands the real Python ecosystem, don't bend the Code node: a FastAPI microservice called via HTTP Request, or a well-guarded Execute Command on self-hosted, will do the job cleanly and durably.

FAQ

Frequently asked questions

Can you actually use Python in n8n?

Yes. The Code node offers a Python (Beta) option that runs CPython compiled to WebAssembly via Pyodide, inside the n8n process itself. The data-access syntax uses an underscore instead of a dollar sign: _input.all() replaces $input.all(). It's real Python, but sandboxed: no direct network or file access, and only the standard library plus the pure-Python packages Pyodide supports are available.

Can you import pandas or numpy in n8n's Python Code node?

Don't count on it. Pyodide supports the Python standard library and a list of precompiled or pure-Python packages, but what's actually importable in the Code node depends on the Pyodide version your n8n release embeds. For real data processing with pandas, the reliable route is a Python microservice (FastAPI, Flask) called via HTTP Request, or the Execute Command node on a self-hosted instance with Python installed.

Why is the Python Code node slower than JavaScript in n8n?

Because the code runs through Pyodide, a CPython-compiled-to-WebAssembly layer that has to initialize before executing, while JavaScript runs natively in n8n's Node.js runtime. On a workflow triggered frequently or processing many items, that startup overhead adds up. It's one of the reasons n8n recommends JavaScript as the default.

Should you pick JavaScript or Python for the Code node?

JavaScript is the recommended default: native execution, faster startup, full coverage of n8n's built-in helpers. Python makes sense when your team is clearly more fluent in it and the logic is pure data processing (transformation, parsing, calculations). As soon as you need scientific libraries, network, or file access, move to an external Python microservice or Execute Command.

Bundle FlowKit Complet

€269