# How to move Pipedream Python steps to n8n

> Source: https://workflowexit.com/pipedream-python-steps-to-n8n · Updated 2026-10-13

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](/pipedream-http-trigger-to-n8n-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:

```python
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:**

```javascript
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:

```python
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](https://docs.n8n.io/deploy/host-n8n/configure-n8n/set-up-task-runners/) 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](/pipedream-code-steps-n8n-cloud).

## What Workflow Exit does with Python steps

The [free report](/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](/pricing), 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](/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](/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](/pipedream-to-n8n) 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.
