Passing data in and out of Python in n8n: the items array explained

7 min read

TL;DR

  • n8n Python integration runs Python via Pyodide (CPython 3.11 compiled to WebAssembly), not a host Python binary.
  • Data flows through a strict items array: each element must be a dict containing a "json" key.
  • Read incoming data with $input.all(); always return a list of {"json": {...}} dicts.
  • For packages outside Pyodide's sandbox, the Execute Command node calling a host Python process is the correct path.
  • As of early 2026, Pyodide covers roughly 75 packages from the scientific Python ecosystem.

The most common failure mode in n8n Python integration has nothing to do with Python syntax: it's a structural mismatch between what developers expect a scripting runtime to accept and what the n8n Code node actually requires. When you drop a Python snippet into a workflow, you're not operating on raw JSON or a plain dict. Every piece of data passing through n8n flows as a list of structured items, each wrapped in a specific schema. Get that schema wrong and the node either silently returns nothing or throws an opaque error. This article explains the exact data contract n8n enforces, why it exists, and how to write Python code that works with it predictably.

What n8n actually runs when you select Python in the Code node

n8n doesn't ship a Python executable. When you select Python in the Code node, the workflow engine delegates execution to Pyodide, a port of CPython compiled to WebAssembly. The n8n v1.0 release in mid-2023 introduced this dependency, and 2025-2026 marks the period where the n8n Pyodide runtime has reached genuine production stability across real-world deployments.

The practical consequences diverge from a standard Python environment in several ways.

No pip install at runtime. Pyodide ships with a curated set of packages pre-compiled to WebAssembly. You can't call pip install inside a Code node. If a package isn't in Pyodide's distribution, it's unavailable, full stop.

No filesystem access. The WebAssembly sandbox has no persistent storage. Reading or writing files with open() either raises an error or operates on an ephemeral in-memory filesystem that doesn't survive between node executions.

No access to the host process. subprocess, os.system, and similar calls are blocked or non-functional. The sandbox is intentionally isolated from the machine running n8n.

Available standard library. Most of the Python stdlib works: json, math, re, datetime, collections, itertools, functools. Modules that interact with the OS (socket, multiprocessing) are restricted or absent.

Scientific packages. As of early 2026, Pyodide covers approximately 75 packages from the scientific Python ecosystem, including numpy, pandas, scipy, scikit-learn, and Pillow. These are compiled ahead of time into the WebAssembly bundle, so import times are acceptable within a workflow node.

The key mental model shift: running Python code in n8n is closer to executing a script inside a browser sandbox than to running it on a server. That constraint shapes every architectural decision downstream. Honestly not sure why they went this route initially, but it does solve the dependency hell problem for most users.

The items array: the data contract every n8n Python integration must respect

Every node in n8n, regardless of type, passes data downstream as an array of items. Each item follows a fixed schema:

[
  {
    "json": { "fieldA": "value", "fieldB": 42 },
    "binary": {}
  }
]

The json key holds the actual payload as a plain object. The binary key is optional and carries file data. For most data transformation tasks, only json matters.

When an n8n Code node Python script runs, n8n exposes the incoming array through two built-in helpers:

  • $input.all() returns the full list of incoming items.
  • $input.first() returns only the first item (shorthand when you know there's exactly one).

Reading input items in Python

items = $input.all()
first_payload = items[0]["json"]        # access the payload of item 0
all_names = [item["json"]["name"] for item in items]

You access the payload via ["json"], not directly on the item dict. This detail is the source of most KeyError failures from developers who assume the item dict is the payload.

Returning valid items from a Python Code node

The return value must be a list of dicts, where each dict contains at minimum a "json" key:

return [{"json": {"result": 42}}]

## Correct: multiple output items
return [{"json": {"row": i}} for i in range(5)]

## Wrong: bare dict (n8n will throw)
return {"result": 42}

## Wrong: list without the json wrapper (silent failure or opaque error)
return [42, 43, 44]

Returning a bare dict instead of a list of {"json": ...} dicts is the single most reported failure pattern on the n8n community forum (community.n8n.io, threads spanning 2024-2025). The error n8n surfaces in this case isn't always explicit, which is why developers spend time debugging Python syntax when the problem is structural.

Each output item can carry a different set of fields. You're not required to preserve the input schema. A Code node can read items with {name, email} fields and return items with {slug, timestamp} fields without any mapping declaration.

Code node vs Execute Command: choosing the right Python execution path

For any n8n Python integration, two execution paths exist.

Pyodide Code node runs inside the n8n process via WebAssembly. Zero external dependencies, no configuration, no Python binary required on the host. Trade-off: limited to the Pyodide package set, no filesystem, no subprocess.

Execute Command node calling Python runs a system Python process on the host machine or Docker container. Full pip ecosystem, filesystem access, arbitrary package installs, ML model loading. Trade-off: requires Python to be installed on the host, and managing the data exchange (passing JSON via stdin or arguments, parsing stdout) is manual work.

A practical threshold:

ScenarioRecommended path
String normalization, date parsing, math transformsPyodide Code node
numpy/pandas computation within the Pyodide package setPyodide Code node
ML inference, transformers, PyTorchExecute Command
File I/O (CSV, PDF, images on disk)Execute Command
Any package absent from Pyodide distributionExecute Command

The n8n Execute Command Python approach requires that your host script read input from stdin or a CLI argument, and write output to stdout as JSON. n8n then parses that stdout back into the items array. This pattern is documented in the n8n Code node reference and was discussed in depth in a community thread from February 2025.

The Execute Command approach also enables calling a FastAPI microservice running on localhost via an HTTP Request node. That's the production-grade exit path for workloads that outgrow the Pyodide sandbox.

Practical data transformation patterns in the Python Code node

One item in, many items out

A single incoming item can expand into multiple output items. The return list simply has more elements than the input:

items = $input.all()
payload = items[0]["json"]

## payload["rows"] is a list of records
return [{"json": row} for row in payload["rows"]]

Input: one item with a rows array of 50 dicts. Output: 50 separate items, each carrying one row. This is the standard pattern for unpacking paginated API responses before routing them through downstream nodes.

Filtering and reshaping JSON payloads

items = $input.all()

return [
    {"json": {"id": item["json"]["user_id"], "label": item["json"]["name"]}}
    for item in items
    if item["json"].get("status") == "active"
]

This combines filtering and field renaming in one pass. Downstream nodes see only active records with a restructured key set.

Attaching a computed field is another common use case for a Python script in n8n workflow. When you want to enrich items without dropping existing fields:

items = $input.all()
output = []
for item in items:
    data = dict(item["json"])           # shallow copy to avoid mutating the original
    data["word_count"] = len(data.get("body", "").split())
    output.append({"json": data})
return output

The dict() copy avoids mutating the original item reference in place, which can produce unexpected behavior when n8n reuses item objects internally across nodes.

Limitations, execution timeouts, and the state of Python support in 2026

Package availability. As of early 2026, Pyodide covers approximately 75 packages from the scientific Python ecosystem, according to the Pyodide packages documentation. This includes numpy, pandas, scipy, scikit-learn, and matplotlib. Arbitrary PyPI packages that the Pyodide team hasn't compiled to WebAssembly are unavailable. There's no fallback: a failing import raises ModuleNotFoundError and halts the workflow execution.

Execution timeouts. On n8n Cloud, the Code node respects the workflow execution timeout, which sits at approximately 120 seconds on Growth and Enterprise plans as of 2025 pricing. Self-hosted deployments configure this via the EXECUTIONS_TIMEOUT environment variable. Long-running inference or file-processing tasks that approach this limit will abort mid-execution with no partial output.

No persistent state. Each Code node execution is stateless. Variables defined in one execution are gone in the next. For caching intermediate results, the correct pattern is to write to a database node (Postgres, Redis, Supabase) and read it back in a later workflow step.

No native Python SDK. As of November 2024, there's no official n8n Python SDK for triggering workflows or inspecting node outputs from an external Python process. Community discussion confirms this remains an open gap. Current patterns rely on REST webhooks or the n8n REST API.

What 2026 may bring. The n8n team has signaled interest in extending Code node capabilities, including broader Pyodide package support and potential bridging to a local Python environment for self-hosted instances. No committed release timeline is public. For workloads that can't wait, the durable architecture is a FastAPI microservice on the same host: n8n calls it via an HTTP Request node, the microservice runs unrestricted Python, returns JSON, and n8n maps the response back into the items array. This approach decouples the Python runtime entirely from n8n's execution model, survives n8n upgrades, and supports the full pip ecosystem. The cost is operational: a separate process to deploy and maintain. For serious n8n automation with Python at scale, that trade-off is almost always justified.

Key takeaways

The items array is the non-negotiable contract for every n8n Python integration: read with $input.all(), return a list of {"json": {...}} dicts. The n8n Pyodide runtime gives you CPython 3.11 with roughly 75 pre-compiled packages, no pip install, no filesystem, and a hard execution timeout. For anything outside those boundaries, Execute Command or a FastAPI microservice is the correct exit. Understanding the items schema eliminates the majority of Code node failures before a single line of Python logic needs debugging.


If you're building with n8n and Python, the items array schema is non-negotiable: get it wrong and your data vanishes silently. The CLI blueprint in the welcome kit shows how to structure tool outputs as JSON contracts that won't break downstream, same pattern n8n enforces.

Get the welcome kit