Skip to content
Skip to main content
Plain unpainted wooden nesting dolls on an olive-green linen table, the largest one opened with the smaller dolls lined up beside it, a metaphor for pandas pulling its own dependencies into the n8n Python Code node
8 min readBy Carlos Aragon

How to Import pandas in the n8n Python Code Node

To use pandas in the n8n Python Code node on self-hosted n8n 2.x, you need a custom n8nio/runners image with pandas installed, an edited /etc/n8n-task-runners.json that allowlists it, and N8N_RUNNERS_ALLOW_TRANSITIVE_IMPORTS set to true in that same file. Miss any one of the three and you get a different error, which is why the forum threads go in circles. I rebuilt the whole thing in Docker today on n8n 2.44.3 and hit every failure on purpose. The one nobody warns you about is the last one: pandas allowlisted, installed, and still blocked.

Why Does Even import json Fail in the Python Code Node?

Because on n8n 2.x the Python Code node runs on a task runner with a locked-down allowlist, and the default list is empty. Not "small". Empty. This is what a one-line import json returned on the stock n8nio/runners:2.44.3 image:

Security violations detected

Line 1: Import of standard library module 'json' is disallowed.
Allowed stdlib modules: none

The image ships its config at /etc/n8n-task-runners.json, and the Python block sets N8N_RUNNERS_STDLIB_ALLOW and N8N_RUNNERS_EXTERNAL_ALLOW to empty strings. The JavaScript runner in the same file gets crypto and moment by default. Python gets nothing. Even open() is gone: calling it returned name 'open' is not defined, because it's on the default built-ins deny list along with eval, exec, getattr and friends.

The Python environment itself is thin, too. The runner's virtualenv (Python 3.13.16) holds the runner code plus certifi, sentry_sdk, urllib3 and websockets. No pandas, no numpy, no requests. So "import pandas" is two problems: the package isn't installed, and it isn't allowed.

Why Is N8N_RUNNERS_EXTERNAL_ALLOW Ignored?

This is the first thing everyone tries, so I tried it too. I set N8N_RUNNERS_STDLIB_ALLOW=* and N8N_RUNNERS_EXTERNAL_ALLOW=*on the runners container and re-ran the same seven workflows. The errors didn't change by a single character: still Allowed stdlib modules: none.

The reason is in the same config file. The launcher only forwards a fixed allowed-env list to the Python process (things like PATH, N8N_RUNNERS_TASK_TIMEOUT and N8N_RUNNERS_MAX_CONCURRENCY), and the allowlists aren't on it. Then env-overrides sets them to empty strings anyway. In external mode, the allowlist lives in the config file, not in your compose environment. The task runner environment variables page says this now, but a lot of tutorials and AI answers still tell you to add the variable to Docker.

Mounting an edited copy of the config fixed the allowlist part right away: import json worked, and import pandas moved on to the honest error, No module named 'pandas'. If your runners never connect at all, that's a different problem, covered in n8n Task Request Timed Out.

Why Does an Allowlisted pandas Still Say "Security Violation Detected"?

I built a runners image with pandas 2.3.3 and requests, allowlisted both, and ran the workflow. The node failed with a new, vaguer title, Security violation detected (singular), and this description buried in the execution data:

Import of external package 'dateutil.relativedelta' is disallowed.
Allowed external packages: pandas, requests

pandas itself was allowed. But pandas imports dateutil internally, and by default the runner checks those internal imports against your allowlist as well. requests did the same thing on chardet, which is an optional dependency requests probes for and isn't even installed. The sandbox error isn't an ImportError, so the try/except ImportErrorrequests wraps around that probe doesn't catch it.

My next move was the obvious one: list every dependency by hand. I allowlisted numpy, dateutil, pytz, tzdata, six, urllib3, idna, certifi, charset_normalizer, chardet on top of pandas and requests. pandas then died on numexpr, another optional accelerator it probes for. requests died on _codecs_tw, a private standard library codec that the stdlib allowlist also gates. That's whack-a-mole, and it breaks again on the next pandas release.

The fix is one line: "N8N_RUNNERS_ALLOW_TRANSITIVE_IMPORTS": "true" in the Python env-overrides. With it, pandas, the CSV test and a live requests.getall passed. And it doesn't open the door as wide as it sounds: a direct import numpyin my own code was still blocked, because the runner only skips the check for imports made by the allowlisted package's code. Your Code node's own imports are always validated.

What Did Each Setup Actually Return?

Same three Code nodes (import json, import pandas, import requests), each behind a webhook, run against six runner setups:

Runner setupimport jsonimport pandasimport requests
Stock runners imageBlocked (stdlib: none)Blocked (external: none)Blocked (external: none)
Stock image + env vars set to *Blocked (stdlib: none)Blocked (external: none)Blocked (external: none)
Stock image + edited configWorksNo module named 'pandas'No module named 'requests'
Custom image + edited configWorksViolation: dateutil.relativedeltaViolation: chardet
Custom image + every dependency listedWorksViolation: numexprViolation: _codecs_tw (stdlib)
Custom image + transitive imports onWorksWorks (2.3.3)Works

Read it top to bottom and you get the order people hit these in. Each fix just shows you the next error, which is why it feels like the setup is broken when it's really three separate locks.

The Setup That Worked

Pin the runners tag to your n8n version. The Dockerfile installs into the runner's own virtualenv with uv, which is already in the image:

FROM n8nio/runners:2.44.3
USER root
RUN cd /opt/runners/task-runner-python && uv pip install pandas==2.3.3 requests
COPY n8n-task-runners.json /etc/n8n-task-runners.json
USER runner

For the config, copy the image's own file out first (docker run --rm --entrypoint cat n8nio/runners:2.44.3 /etc/n8n-task-runners.json) and only edit the Python block's env-overrides. Don't write a three-line file from scratch; the launcher reads the command, args and health-check port from it too.

{
  "runner-type": "python",
  ...keep the image's command, args, allowed-env as they are...
  "env-overrides": {
    "N8N_RUNNERS_STDLIB_ALLOW": "io,json",
    "N8N_RUNNERS_EXTERNAL_ALLOW": "pandas,requests",
    "N8N_RUNNERS_ALLOW_TRANSITIVE_IMPORTS": "true"
  }
}

Then the two services. I ran this exact compose and the test workflow returned {"totals":{"A":160,"B":80}}:

services:
  n8n:
    image: n8nio/n8n:2.44.3
    ports: ["5678:5678"]
    environment:
      - N8N_RUNNERS_MODE=external
      - N8N_RUNNERS_AUTH_TOKEN=change-me
      - N8N_RUNNERS_BROKER_LISTEN_ADDRESS=0.0.0.0
      - N8N_ENCRYPTION_KEY=your-key
    volumes: ["n8n_data:/home/node/.n8n"]
  task-runners:
    build:
      context: .
      dockerfile: Dockerfile.runners
    environment:
      - N8N_RUNNERS_TASK_BROKER_URI=http://n8n:5679
      - N8N_RUNNERS_AUTH_TOKEN=change-me
    depends_on: [n8n]
volumes:
  n8n_data:

The Code node I used to prove it, with the language set to Python:

import io
import pandas as pd

df = pd.read_csv(io.StringIO("lead,amount\nA,120\nB,80\nA,40"))
totals = df.groupby("lead")["amount"].sum().to_dict()
return [{"json": {"totals": {k: int(v) for k, v in totals.items()}}}]

Note the import io. Every stdlib module you touch has to be on N8N_RUNNERS_STDLIB_ALLOW, even when pandas is happy. You can set it to * if you trust everyone who can edit workflows, but on a shared instance I'd list them.

Is pandas Worth It in an n8n Workflow?

Sometimes. It costs you twice. The image grew from 878 MB to 1.04 GB, and the virtualenv went to 116 MB. The bigger cost is time: across 10 webhook runs each, plain Python returned in about 0.13 seconds, and the pandas CSV job took 0.97 to 1.20 seconds, median 1.1. It never got faster on repeat runs, because the runner forks a fresh process for every task, so every execution imports pandas again.

A second per execution is nothing on a nightly report. On a webhook that fires per lead, or a loop that calls the Code node per item, it adds up fast and it's on top of whatever your queue mode workers are already doing. My rule: if the transformation fits in a list comprehension, skip pandas. If you're pivoting a few thousand rows or doing real date math, it earns its second.

And if you're on n8n Cloud, none of this applies, because you don't control the runner image. Use the JavaScript Code node, the built-in transform nodes, or put your Python behind a small API and call it with HTTP Request. That's one of the trade-offs I weighed in n8n Cloud vs self-hosted. If you're still on 1.x with the old Pyodide Python, plan this before you take the next major upgrade.

How I tested this: October 22, 2026, n8n 2.44.3 and n8nio/runners 2.44.3 in external mode on Docker Desktop (arm64), Mac mini in Allen, Texas. Seven workflows imported and published with the n8n CLI, each a Webhook node plus a Python Code node, triggered with curl. Error text read from execution_data in the SQLite database. Custom image: pandas 2.3.3 (pulled numpy 2.5.4) and requests 2.34.2. Timings are full webhook round trips on localhost, 10 runs each.

n8n Python Code Node FAQ

Why does import json fail in the n8n Python Code node?

n8n 2.x blocks every Python standard library module by default. The stock n8nio/runners image sets N8N_RUNNERS_STDLIB_ALLOW to an empty string, so the error reads 'Import of standard library module json is disallowed. Allowed stdlib modules: none'. Add the modules you need to the python env-overrides in /etc/n8n-task-runners.json.

Why is N8N_RUNNERS_EXTERNAL_ALLOW ignored on my runners container?

In external mode the runner launcher only passes a fixed list of environment variables to the Python runner, and the config file's env-overrides set both allowlists to empty strings. On n8n 2.44.3, setting N8N_RUNNERS_STDLIB_ALLOW=* on the container produced exactly the same 'Allowed stdlib modules: none' error. Edit the env-overrides in n8n-task-runners.json instead.

Why does pandas fail with Security violation detected even after I allowlisted it?

pandas imports other packages internally, and with N8N_RUNNERS_ALLOW_TRANSITIVE_IMPORTS off those imports are checked against your allowlist too. In my test it failed on dateutil.relativedelta. Set N8N_RUNNERS_ALLOW_TRANSITIVE_IMPORTS to true in the python env-overrides; your own code's imports are still checked.

Is pandas slow in the n8n Code node?

It costs about a second per execution. A small read_csv plus groupby took a median 1.1 seconds per webhook run against 0.13 seconds for plain Python on n8n 2.44.3, and it didn't get faster on repeat runs because each task runs in a freshly forked process that imports pandas again.

Can I use pandas in the Python Code node on n8n Cloud?

Not with this method, because on n8n Cloud you don't control the runner image or its config file. Use the JavaScript Code node, the built-in data transformation nodes, or call your own Python service with the HTTP Request node.

Stuck Between n8n and Your Python Code?

I run self-hosted n8n in production and build the custom runners, queue workers and Python services around it. Tell me what your workflow needs to import and I'll tell you whether it belongs in a Code node or next to one.

Related Posts