> ## Documentation Index
> Fetch the complete documentation index at: https://docs.pflow.run/llms.txt
> Use this file to discover all available pages before exploring further.

# Code

> Execute Python for data transformation

<Note>
  **Agent commands.** Your AI agent uses this node in workflows. You don't configure it directly.
</Note>

The code node runs Python in-process with direct access to input data as native objects. Use it when the logic is deterministic — filtering, reshaping, merging, computing values. If you can write a Python expression for it, there's no reason to burn tokens on an LLM call.

## Parameters

| Parameter  | Type | Required | Default | Description                                                                                                            |
| ---------- | ---- | -------- | ------- | ---------------------------------------------------------------------------------------------------------------------- |
| `code`     | str  | Yes      | -       | Python code (or path to `.py` file, e.g., `./scripts/transform.py`) with type-annotated inputs and a `result` variable |
| `inputs`   | dict | No       | `{}`    | Variable name to value mapping (template variables go here)                                                            |
| `timeout`  | int  | No       | `30`    | Maximum execution time in seconds                                                                                      |
| `requires` | list | No       | `[]`    | Package dependencies (documentation-only, not enforced)                                                                |

## Output

| Key      | Type | Description                                    |
| -------- | ---- | ---------------------------------------------- |
| `result` | any  | Value of the `result` variable after execution |
| `stdout` | str  | Captured `print()` output                      |
| `stderr` | str  | Captured stderr output                         |
| `error`  | str  | Error message (only on failure)                |

## Type annotations

Every input variable and the `result` variable must have a type annotation. pflow uses these to validate inputs before your code runs — so a wrong type gets caught immediately with a fix suggestion, not as a cryptic Python error mid-workflow.

```python theme={null}
data: list            # required for every input
limit: int            # required for every input
result: dict = {}     # required on result too
```

<Tip>
  Use `Any` when you don't know or care what the input type is. pflow auto-injects `Any`, so you don't need `from typing import Any`.
</Tip>

<Accordion title="Supported types">
  | Annotation | Python type    | Notes                                                      |
  | ---------- | -------------- | ---------------------------------------------------------- |
  | `int`      | `int`          |                                                            |
  | `float`    | `int`, `float` | Integers are accepted where float is declared              |
  | `str`      | `str`          |                                                            |
  | `bool`     | `bool`         |                                                            |
  | `list`     | `list`         | Generic params like `list[dict]` check outer type only     |
  | `dict`     | `dict`         | Generic params like `dict[str, Any]` check outer type only |
  | `set`      | `set`          |                                                            |
  | `tuple`    | `tuple`        |                                                            |
  | `bytes`    | `bytes`        |                                                            |
  | `Any`      | —              | Auto-injected. Skips type validation entirely              |

  Only the outer type is checked — `list[dict]` validates that the value is a `list`, but doesn't check whether the elements are dicts.
</Accordion>

## Template placement

<Warning>
  **Templates go in `inputs`, never in the code block.** The code block is literal Python — `${var}` isn't valid Python syntax, so putting templates there causes a parse error.
</Warning>

````markdown theme={null}
### transform

Filter active users from the API response.

- type: code
- inputs:
    data: ${fetch.response}

```python code
data: list

result: list = [u for u in data if u['active']]
```
````

Templates in the `inputs` dict get resolved before your code runs. By the time Python sees `data`, it's already a native object — no parsing needed.

## Bridge to workflow inputs

Workflow `## Inputs` / `## Outputs` use canonical names. Code blocks use Python annotations.

| Workflow `type:` | Code annotation           |
| ---------------- | ------------------------- |
| `string`         | `str`                     |
| `integer`        | `int`                     |
| `number`         | `int \| float` or `float` |
| `boolean`        | `bool`                    |
| `array`          | `list`                    |
| `object`         | `dict`                    |
| `any`            | `Any`                     |

## Inputs are native objects

Upstream JSON is auto-parsed before your code runs. If the source node produced a JSON string, declare `dict` or `list`, not `str` — you get real Python objects, not strings.

| Upstream output          | Declare as       | You get         |
| ------------------------ | ---------------- | --------------- |
| JSON string `'{"a": 1}'` | `dict`           | `{"a": 1}`      |
| JSON array `'[1, 2, 3]'` | `list`           | `[1, 2, 3]`     |
| Plain text               | `str`            | The text string |
| Number                   | `int` or `float` | The number      |

## Examples

### Simple transformation

````markdown theme={null}
### filter-active

Filter users to only active accounts.

- type: code
- inputs:
    users: ${fetch-users.response.data}

```python code
users: list

result: list = [u for u in users if u['status'] == 'active']
```
````

### Multiple inputs

````markdown theme={null}
### merge-sources

Combine data from two upstream nodes.

- type: code
- inputs:
    api_data: ${fetch-api.response}
    db_records: ${query-db.result}

```python code
api_data: list
db_records: list

merged = api_data + db_records
result: dict = {
    'items': merged,
    'count': len(merged),
}
```
````

Access fields downstream: `${merge-sources.result.items}`, `${merge-sources.result.count}`.

### No inputs

````markdown theme={null}
### generate-range

Generate a sequence with no external inputs.

- type: code

```python code
result: list = list(range(1, 11))
```
````

### Using subprocess

````markdown theme={null}
### get-git-log

Fetch recent git history.

- type: code
- timeout: 60

```python code
import subprocess

output = subprocess.run(
    ['git', 'log', '--oneline', '-10'],
    capture_output=True, text=True, check=True
).stdout
result: str = output.strip()
```
````

### Imports

Standard library imports work without restrictions — `json`, `re`, `subprocess`, `pathlib`, `datetime`.

Third-party packages work too, but they need to be installed in pflow's environment.

<Accordion title="Installing third-party packages">
  pflow runs in an isolated environment, so `pip install pandas` won't work. How you add packages depends on how you installed pflow:

  **pipx** (simpler — additive, doesn't touch existing packages):

  ```bash theme={null}
  pipx inject pflow-cli pandas
  ```

  **uv tool** (replaces the full environment — list existing extras first):

  ```bash theme={null}
  # Check what's already installed so you don't lose it
  uv tool list --show-with

  # Include ALL extras — both new and existing
  uv tool install --with pandas --with llm-openrouter pflow-cli
  ```

  <Warning>
    `uv tool install` is not additive — it recreates pflow's environment from scratch. If you previously installed with `--with llm-openrouter` and now run `--with pandas` alone, you'll lose `llm-openrouter`. Always run `uv tool list --show-with` first and include all existing `--with` flags.
  </Warning>

  A simpler way to manage code node dependencies is coming in a future release.
</Accordion>

````markdown theme={null}
### parse-dates

Parse and sort date strings.

- type: code
- inputs:
    raw_dates: ${fetch.response.dates}

```python code
from datetime import datetime

raw_dates: list

parsed = [datetime.fromisoformat(d) for d in raw_dates]
parsed.sort()
result: list = [d.isoformat() for d in parsed]
```
````

## Security

<Warning>
  Code runs directly in the pflow process via `exec()` without sandboxing. It has full access to your filesystem, network, and standard library — same as running a Python script yourself. Sandboxed execution is planned for a future release.
</Warning>

<Tip>
  **You're in control.** Your agent asks before running workflows, and you can open any `.pflow.md` file to see exactly what code will run. To disable this node entirely: `pflow settings deny code`.
</Tip>

### External code file

For larger scripts, reference an external Python file. The file is read and used as the code block. Path is relative to the workflow file.

```markdown theme={null}
### transform

Transform data using external script.

- type: code
- code: ./scripts/transform.py
- inputs:
    data: ${fetch.response}
```

## Error handling

Errors include line numbers, the actual source line, and a suggestion for how to fix it. For example, a type mismatch looks like:

```
Input 'data' expects list but received dict

Suggestions:
  - Change the type annotation to: data: dict
  - Or convert the input value to list
```

| Error              | Cause                                   | What you see                                 |
| ------------------ | --------------------------------------- | -------------------------------------------- |
| Missing annotation | Input or result has no type annotation  | Which variable needs an annotation           |
| Type mismatch      | Input value doesn't match declared type | Expected vs actual type, with fix suggestion |
| `NameError`        | Undefined variable                      | Variable name, suggestion to add to inputs   |
| `ImportError`      | Module not installed                    | Module name, install command                 |
| Timeout            | Code exceeded time limit                | Current timeout, suggestion to increase      |
| `SyntaxError`      | Invalid Python                          | Line number from Python parser               |
| Runtime error      | Any other exception                     | Exception type, line number, source line     |

The node doesn't retry — code execution is deterministic, so retrying the same code produces the same result.
