WEWorkflow ExitFree report

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:

  1. A Webhook node named trigger.
  2. A Filter node that keeps items where {{ $json.body.email }} is not empty.
  3. An HTTP Request node: GET https://api.example.com/v1/people, with the query parameter email set to {{ $json.body.email }} and a Header Auth credential.
  4. 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 json or datetime.
  • 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

  1. Note what it imports, and whether you're moving to n8n Cloud or self-hosted n8n.
  2. List every pd.steps reference and decide where that data will come from in n8n.
  3. Move API calls that use connected accounts into HTTP Request nodes with credentials.
  4. Replace os.environ with Variables or credentials.
  5. Replace pd.respond() with a Respond to Webhook node and pd.flow.exit() with return [].
  6. 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.