How to move Pipedream Python steps to n8n
Updated October 13, 2026
n8n's Code node can run Python, but not a Pipedream Python step as written. On n8n Cloud, Python code can't import anything, including requests, json and os. On self-hosted n8n 2.x, Python runs on task runners, and imports work only for modules you install in the runner image and allowlist. In both cases the code sees only the node's input items. There is no pd.steps, pd.inputs or pd.export. For most Pipedream Python steps the quickest route is a rewrite in JavaScript, which can read any earlier node with $('name') and call APIs with this.helpers.httpRequest. Keep Python when you self-host and depend on Python libraries.
What a Pipedream Python step relied on
A Pipedream Python step is a handler(pd) function, and the pd object did much of the work:
| Pipedream | What it did |
|---|---|
pd.steps["trigger"]["event"] |
Read the trigger event and earlier steps |
return {...} |
Became the step's result, read later as $return_value |
pd.export("name", value) |
Exported a named value |
pd.inputs["slack"]["$auth"] |
Read a connected account, other props and data stores |
return pd.flow.exit("reason") |
Ended the workflow early |
pd.respond({"status": 200, "body": ...}) |
Sent the HTTP response, with status and body required |
os.environ["KEY"] |
Read an environment variable |
import requests |
Any PyPI package, installed on deploy |
/tmp |
Scratch files during a run |
On n8n Cloud, imports are the first thing to break. Of 59 Python steps we analyzed in public GitHub Sync repositories, 33 call requests and 25 read os.environ. Neither works on n8n Cloud.
What n8n's Python Code node allows
| n8n Cloud | Self-hosted n8n 2.x | |
|---|---|---|
| Python available | Yes | Yes, with task runners in external mode (the n8nio/runners image) |
| Standard library | No imports | Modules you allowlist |
| PyPI packages | No imports | Packages installed in the runner image and allowlisted |
| Data the code sees | _items (all items) or _item (one item) |
Same |
| Read another node by name | No | No |
Static data, $vars, other helpers |
No | No |
| Field access | Brackets only: item["json"]["email"] |
Same |
A few more details:
- The older Pyodide-based Python was removed in n8n 2.0. Native Python has been available since n8n 1.111.0 and is stable as of n8n 2.
- Native Python accepts only bracket access. Code written for Pyodide with dot access, such as
item.json.email, breaks. - Native Python blocks insecure built-ins by default.
- On a standard self-hosted install without the runners image, a Python Code node fails with a "Python runner unavailable" error.
Translate the pd object
| Pipedream Python | n8n Python | n8n JavaScript |
|---|---|---|
pd.steps["trigger"]["event"]["body"] |
_items[0]["json"]["body"], if the node comes right after the Webhook |
$('trigger').first().json.body |
pd.steps["lookup"]["$return_value"] |
Only if lookup feeds this node directly |
$('lookup').first().json |
return {"a": 1} |
return [{"json": {"a": 1}}] |
return [{ json: { a: 1 } }] |
pd.export("total", 9) |
Add total to the returned item |
Same |
return pd.flow.exit("done") |
return [] |
return [] |
pd.respond(...) |
A Respond to Webhook node | Same |
pd.inputs["github"]["$auth"] |
Not possible: Code nodes can't read credentials | Same, so use an HTTP Request node with a credential |
A data store through pd.inputs |
No equivalent in Python | $getWorkflowStaticData("global"), or a Data Table node |
os.environ["KEY"] |
Not available | $vars.KEY on plans with Variables |
requests.get(url, params=p) |
An HTTP Request node before this one | this.helpers.httpRequest({ url, qs: p }) |
Example: one step, three ways
A typical Pipedream Python step looks up a person by email and stops when the email is missing:
import os
import requests
def handler(pd: "pipedream"):
email = pd.steps["trigger"]["event"]["body"].get("email")
if not email:
return pd.flow.exit("no email")
resp = requests.get(
"https://api.example.com/v1/people",
params={"email": email},
headers={"Authorization": f"Bearer {os.environ['PEOPLE_API_KEY']}"},
timeout=10,
)
resp.raise_for_status()
person = resp.json()
return {"email": email, "company": person.get("company")}
1. A JavaScript Code node, which works on n8n Cloud:
const email = $('trigger').first().json.body?.email;
if (!email) return []; // like pd.flow.exit: nothing after this node runs
const person = await this.helpers.httpRequest({
method: 'GET',
url: 'https://api.example.com/v1/people',
qs: { email },
headers: { Authorization: `Bearer ${$vars.PEOPLE_API_KEY}` },
timeout: 10000,
json: true,
});
return [{ json: { email, company: person.company ?? null } }];
$vars needs n8n Variables, which come with n8n Cloud Pro and Enterprise and self-hosted Business and Enterprise. On other plans, keep the key in a Header Auth credential and make the call from an HTTP Request node.
2. Python without imports, on n8n Cloud. Move the network call out of the code:
- A Webhook node named
trigger. - A Filter node that keeps items where
{{ $json.body.email }}is not empty. - An HTTP Request node: GET
https://api.example.com/v1/people, with the query parameteremailset to{{ $json.body.email }}and a Header Auth credential. - A Python Code node that only reshapes the response:
results = []
for item in _items:
person = item["json"]
results.append({"json": {"email": person.get("email"), "company": person.get("company")}})
return results
The Python node sees only the HTTP Request node's output. If you need the original email and the API doesn't return it, merge it in with another node first.
3. Python with requests, on self-hosted n8n. Build a runner image that includes requests, allowlist it with N8N_RUNNERS_EXTERNAL_ALLOW, and allowlist any standard-library modules with N8N_RUNNERS_STDLIB_ALLOW. n8n's task runner setup guide has the details. You still replace pd.steps with input items and pd.flow.exit() with return []. Python Code nodes can't read n8n credentials or Variables either, so a call that needs an API key usually fits better in an HTTP Request node.
When to rewrite in JavaScript
Rewrite the step in JavaScript when any of these is true:
- You're on n8n Cloud and the step imports anything, even
jsonordatetime. - It reads steps other than the one directly before it through
pd.steps. - It uses a connected account (
$auth) or a data store.
Keep Python when you self-host, the step depends on Python libraries you can install in the runner image, and it needs nothing but its own input items.
Common translations:
| Python | JavaScript in an n8n Code node |
|---|---|
json.loads(text) |
JSON.parse(text) |
datetime.now(timezone.utc) |
DateTime.now().toUTC() (Luxon is built in) |
hashlib.sha256(data).hexdigest() |
require('crypto').createHash('sha256').update(data).digest('hex') |
base64.b64encode(data) |
Buffer.from(data).toString('base64') |
re.search(r"\d+", text) |
text.match(/\d+/) |
requests.post(url, json=payload) |
this.helpers.httpRequest({ method: 'POST', url, body: payload, json: true }) |
time.sleep(30) |
A Wait node between two steps |
n8n Cloud allows require('crypto'). On self-hosted n8n, depending on how your task runners are set up, you may need to allow it with NODE_FUNCTION_ALLOW_BUILTIN=crypto. For Node.js steps, see what breaks on n8n Cloud.
What Workflow Exit does with Python steps
The free report flags every Python step and lists what it imports, split into standard-library modules and third-party packages, with what that means on n8n Cloud. It also names the task runner settings that allow those modules on self-hosted n8n.
The converter doesn't run Python through its compatibility layer. Without the AI rewrite, a Python step becomes a Code node that stops with a TODO error and keeps your original code in a comment, so nothing is lost. With the optional AI rewrite in the paid conversion, the step is ported to JavaScript that runs through the same layer as Node.js steps, so steps, exports, early exits and data stores keep working. The result is checked against your target's import rules before you download it.
Data stores need their records copied out separately; see saving Pipedream data stores.
Checklist for each Python step
- Note what it imports, and whether you're moving to n8n Cloud or self-hosted n8n.
- List every
pd.stepsreference and decide where that data will come from in n8n. - Move API calls that use connected accounts into HTTP Request nodes with credentials.
- Replace
os.environwith Variables or credentials. - Replace
pd.respond()with a Respond to Webhook node andpd.flow.exit()withreturn []. - Test with saved sample events and compare the output with Pipedream's.
The free migration report shows every Python step in your export and what it imports, so you know before you start how many need a rewrite for n8n Cloud. It runs in your browser, and the Pipedream to n8n guide covers the rest of the move.
Frequently asked questions
Can n8n Cloud run Python code?+
Yes, but the Python Code node on n8n Cloud can't import any library, not even the standard library. Plain Python that reshapes its input items works. Code that needs requests, json or os doesn't.
How do I read earlier steps in an n8n Python Code node?+
Not by name. Native Python only sees the node's own input items, as _items or _item. Put the data you need on those items with earlier nodes, or write the step in JavaScript, which can read any earlier node with $('name').
Can I use requests in n8n?+
Not on n8n Cloud. On self-hosted n8n, install it in the task runner image and allowlist it. Often it's simpler to make the call with an HTTP Request node, or with this.helpers.httpRequest in a JavaScript Code node.
Why does self-hosted n8n say the Python runner is unavailable?+
The standard n8n image doesn't include Python. Native Python runs on task runners in external mode, which use n8n's separate runners image, so you need to add that container.
Does Workflow Exit convert Python steps?+
The free report flags every Python step and lists its imports. The paid conversion's optional AI rewrite ports Python steps to JavaScript. Without it, a Python step becomes a Code node with a TODO and your original code kept as a comment.