
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: noneThe 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, requestspandas 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 setup | import json | import pandas | import requests |
|---|---|---|---|
| Stock runners image | Blocked (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 config | Works | No module named 'pandas' | No module named 'requests' |
| Custom image + edited config | Works | Violation: dateutil.relativedelta | Violation: chardet |
| Custom image + every dependency listed | Works | Violation: numexpr | Violation: _codecs_tw (stdlib) |
| Custom image + transitive imports on | Works | Works (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 runnerFor 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
n8n
n8n Task Request Timed Out After 60 Seconds: How to Fix
The n8n Code node error means no task runner picked up the task; your code never started. I reproduced it on n8n 2.38.3 with external runners: broker on 127.0.0.1, a mismatched auth token, and the log line that tells each cause apart.
n8n
n8n Durable Scheduler: What Changes for Schedule Triggers
n8n 2.45 turns the durable scheduler on by default. Tested on 2.44.3: a 26-second restart lost zero runs, a 3-minute outage dropped 16 of 17, only v1.4 Schedule Triggers can catch up, and every-N-minutes rules now run a fixed gap from activation instead of on the clock.
n8n
n8n Anthropic Prompt Caching: When the Toggle Pays Off
n8n 2.37 added Prompt Caching to the Anthropic Chat Model and 2.42 to the Anthropic node. It uses automatic caching, so agent tool loops get about 67% cheaper input while one-shot workflows pay 25% more for cache writes nobody reads. Where the toggle lives, why reads stay at 0, and the HTTP Request fix.