> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.runtype.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.runtype.com/_mcp/server.

# Using transform-data steps

Use a transform-data step to run code that formats, filters, combines, or calculates Flow data. The **Flow editor** labels this step **Run Code**.

## Before you begin

Open a Flow in the **Flow editor**. For instructions, see [Creating and editing flows](/user-guide/flows/creating-and-editing-flows).

## Add a transform-data step

To add and configure a transform-data step, follow these steps:

1. In the **Flow editor**, click the **+** control where you want to insert the step.
2. In the step picker, select **Run Code**. The step card opens.
3. In **Execution Environment**, choose the environment that matches your code. The default **Cloudflare Worker (Default)** environment runs JavaScript.
4. In **Input Mode**, select **Code (Write your own logic)**.
5. Select a code template or click **Write code** to open the editor.
6. In the code editor, write a script that returns the value that you want to store.
7. In **Output variable**, enter the name for the returned value.
8. In the toolbar, click **Create** for a new Flow or **Save** for an existing Flow.
9. Run the Flow. The output variable holds the value that the script returned.

Optional: Rename the step in its header to describe its transformation.

The step inspector shows the same settings with shorter labels: **Runtime** for the execution environment, and **Code** or **Variable** for the input mode.

To check the output without a full run, see [Test a transformation](#test-a-transformation).

## Choose an execution environment

The following table compares the execution environments:

| Environment                     | Languages                      | Packages      | Helper functions           |
| ------------------------------- | ------------------------------ | ------------- | -------------------------- |
| **Cloudflare Worker (Default)** | JavaScript                     | Not supported | In the `helpers` namespace |
| **Runtype Sandbox**             | JavaScript, TypeScript, Python | Supported     | Not available              |
| **Daytona Sandbox**             | JavaScript, TypeScript, Python | Supported     | Not available              |
| **QuickJS (Legacy)**            | JavaScript, synchronous only   | Not supported | As global functions        |

To run TypeScript or Python, choose **Runtype Sandbox** or **Daytona Sandbox**, then set **Language** to the language that you want to use.

**Cloudflare Worker (Default)** supports `async`/`await` and blocks network access by default. Its helper functions include `helpers.slugify`, `helpers.extractEmails`, `helpers.html2markdown`, and `helpers.formatDate`. **QuickJS (Legacy)** exposes the same functions as globals, such as `slugify(text)`. For the full list, see [Cloudflare Worker helper functions](/user-guide/tools/creating-custom-tools#cloudflare-worker-helper-functions).

Runtype bills **Runtype Sandbox** runs as usage, based on how long the container runs. Use **Cloudflare Worker (Default)** for JavaScript that does not need packages or another language. For more information, see [Billing and plans](/user-guide/settings/billing-and-plans).

**Daytona Sandbox** uses the Daytona API key that you add in **Settings > Integrations**. If you don't add a key, Runtype uses a platform key when one is available for your organization. If no key is available, the step fails with `Daytona API key not configured`.

For HTTP requests that need step-level request and response configuration, use the **Fetch URL** or **Make API Call** step.

### Install packages

To install npm packages for a JavaScript or TypeScript script in a container environment, enter the package metadata in **Package.json (Optional)**. **Runtype Sandbox** ignores this field when **Language** is **Python**.

On **Runtype Sandbox**, the package installation obeys the step's **Network Access** setting. Allow the package registry, such as `registry.npmjs.org`, or choose **Unrestricted**. If the installation fails, the step fails with `npm install failed` and the installer's output.

### Reuse a sandbox

By default, a container environment removes its sandbox when the step finishes. To keep the sandbox, select **Persist Sandbox** (**Keep sandbox alive for reuse**). The step writes the sandbox ID to the `_sandboxId` variable. To run a later step in that sandbox, enter `{{_sandboxId}}` in **Reuse Sandbox ID**.

### Script time limits

A **Runtype Sandbox** script runs until the step timeout, which is 5 minutes unless the run sets another value. When the run sets a step timeout, the script gets that time, from a minimum of 30 seconds to a maximum of 10 minutes. Package installation has its own limit of the same length. A **Cloudflare Worker (Default)** script runs until the step timeout, which is 5 minutes unless the run sets another value. A **QuickJS (Legacy)** script runs for up to 5 seconds unless the run sets a step timeout. To set the step timeout for a run, see [Timeouts and long-running work](/user-guide/flows/timeouts-and-long-running-work).

## Control network access

**Cloudflare Worker (Default)** and **Runtype Sandbox** block outbound requests by default. To change this, follow these steps:

1. On the step card, expand **Advanced**. In the step inspector, click the network chip, such as **Network: off**, instead.
2. In **Network Access**, choose one of these options:
   * **Blocked (default)**: Block every outbound request.
   * **Unrestricted**: Allow requests to any public host.
   * **Allowed Hosts Only**: Allow requests only to the hosts in **Allowed Hostnames**.
3. If you chose **Allowed Hosts Only**, enter each hostname in **Allowed Hostnames** and press Enter. A wildcard such as `*.example.com` matches every subdomain. Until you add a hostname, the step still blocks every request.

In a Flow definition, the same options are the `networkAccess` values `"off"`, `"on"`, and `{ "allowedHostnames": ["api.example.com"] }`.

A blocked request does not throw. It returns an HTTP `403` response. Under **Blocked (default)**, the response body starts with `Network access is disabled for this`. Under **Allowed Hosts Only**, the body starts with `Host 'HOST' is not in the sandbox networkAccess allowlist`, where `HOST` is the blocked hostname. To allow the host, add it to **Allowed Hostnames**. Check the response status in your script so that a blocked request does not pass as data. On **Runtype Sandbox**, private-network addresses, loopback addresses, cloud metadata hosts, and `*.internal` hosts stay blocked under every setting.

**Daytona Sandbox** does not use this setting. Outbound access from a Daytona container follows the Daytona sandbox policy.

Outbound WebSockets work on **Cloudflare Worker (Default)** and **Runtype Sandbox** after you enable network access, and the host must be allowed like any other request.

On **Cloudflare Worker (Default)**, open the connection with `fetch()` and an `Upgrade: websocket` header, then accept the returned socket:

```javascript
const response = await fetch('https://example.com/socket', {
  headers: { Upgrade: 'websocket' },
})
const socket = response.webSocket
socket.accept()
```

On **Runtype Sandbox**, use the Node `WebSocket` global:

```javascript
const socket = new WebSocket('wss://example.com/socket')
```

## Write a script

Write a script that produces the value that you want to store. How the script reads Flow variables and returns its result depends on the environment.

### JavaScript on Cloudflare Worker and QuickJS

Return the value from the script. Earlier step outputs are available by their `outputVariable` names, and the complete variable map is available as `input`. Use `input.NAME` when a variable name is not a valid JavaScript identifier or is a reserved word. The examples on this page use `input` so that they work for every variable name.

The following script formats a customer object:

```javascript
return {
  name: input.customer_data.name.toUpperCase(),
  email: input.customer_data.email.toLowerCase(),
  timestamp: new Date().toISOString(),
}
```

Replace `customer_data` with the earlier step's `outputVariable` name.

The following script counts words in a generated summary:

```javascript
const summary = input.generate_summary.trim()
const wordCount = summary ? summary.split(/\s+/).length : 0

return {
  summary,
  wordCount,
  readingTime: `${Math.ceil(wordCount / 200)} minutes`,
}
```

Replace `generate_summary` with the earlier step's `outputVariable` name.

### Scripts on Runtype Sandbox

**Runtype Sandbox** provides the Flow variables as `parameters`, an object in JavaScript and TypeScript and a dictionary in Python. It does not define `input` or top-level variable names.

A JavaScript or TypeScript script returns its value:

```javascript
return { wordCount: parameters.generate_summary.trim().split(/\s+/).length }
```

A Python script prints its value. The step parses the printed output as JSON when it is valid JSON and stores it as text otherwise, so print structured results with `json.dumps`:

```python
import json

words = parameters["generate_summary"].split()
print(json.dumps({"wordCount": len(words)}))
```

Replace `generate_summary` with the earlier step's `outputVariable` name.

### Scripts on Daytona Sandbox

**Daytona Sandbox** provides the complete variable map as `input` and each variable with a valid name at the top level. In every language, the script prints its result, and the step parses the printed output as JSON when it is valid JSON:

```python
import json

print(json.dumps({"wordCount": len(input["generate_summary"].split())}))
```

### Run code from a variable

To run code that an earlier step generated, set **Input Mode** to **Variable (From template variable)**. In **Script Variable**, enter the variable that holds the code, such as `{{generated_code}}`.

### Call tools from a script

When you select tools for the step, the code templates include **Call a Tool**. Each selected tool is an `async` function named after the tool, with hyphens and other special characters replaced by underscores, such as `await my_tool({ query: input.searchQuery })`. Tool calls work only on **Cloudflare Worker (Default)**. On other environments, validation shows the `TRANSFORM_POOL_TOOLS_ON_CONTAINER_PROVIDER` warning and any tool call fails when the step runs.

### Use authenticated APIs

Transform-data scripts cannot read managed secrets. Make authenticated requests in a **Fetch URL** or **Make API Call** step, then transform the response in a transform-data step. For more information, see [Managing secrets](/user-guide/settings/managing-secrets).

## Common transformations

Use the following patterns to transform arrays, objects, strings, and numbers. They use the `input` form for **Cloudflare Worker (Default)**.

### Filter and map arrays

Filter an array and create a new object for each remaining item:

```javascript
const highValueOrders = input.order_data.orders
  .filter((order) => order.amount > 1000)
  .map((order) => ({
    id: order.id,
    total: order.amount,
    formattedTotal: '$' + order.amount.toFixed(2),
  }))

return highValueOrders
```

Replace `order_data` with the variable that contains the order list.

### Merge objects

Combine values from multiple sources into one object:

```javascript
return {
  ...input.customer_data,
  ...input.order_history,
  enrichedAt: new Date().toISOString(),
}
```

Replace `customer_data` and `order_history` with the variables that you want to merge.

### Format strings

Create a name and a URL-friendly slug from contact information:

```javascript
const name = `${input.contact_info.firstName} ${input.contact_info.lastName}`
const slug = helpers.slugify(name)

return { name, slug }
```

Replace `contact_info` with the variable that contains the contact information.

### Calculate values

Calculate a cart subtotal, tax, and total:

```javascript
const items = input.cart.items
const subtotal = items.reduce((sum, item) => sum + item.price, 0)
const tax = subtotal * 0.08
const total = subtotal + tax

return { subtotal, tax, total }
```

Replace `cart` with the variable that contains the cart items.

## Use available JavaScript features

JavaScript transform scripts support these features:

* Array methods such as `map`, `filter`, and `reduce`.
* Destructuring and spread operators.
* Arrow functions.
* Template literals.
* `async`/`await` in every environment except **QuickJS (Legacy)**.
* `Date` objects.
* `Math` functions.
* `JSON.parse` and `JSON.stringify`.
* Helper functions on **Cloudflare Worker (Default)** and **QuickJS (Legacy)**.

External libraries are unavailable in **Cloudflare Worker (Default)** and **QuickJS (Legacy)**. Choose **Runtype Sandbox** or **Daytona Sandbox** when your script needs packages.

## Run a coding agent in the step

**Runtype Sandbox** preinstalls the `opencode`, `pi`, `fx`, `claude` (Claude Code), and `codex` (Codex) command-line coding agents. For versions and default models, see [Preinstalled sandbox software](/developer-guides/guides/sandbox-preinstalled-software#coding-agents). The agents can call models only when the step sets `inference` to `true`.

The **Flow editor** has no control for `inference`. Set it in the step's config when you create the Flow through the API or manage it as code. For more information, see [Manage flows as code](/user-guide/flows/manage-flows-as-code).

The following step runs `opencode` from a Python script and prints its output:

```json
{
  "type": "transform-data",
  "config": {
    "sandboxProvider": "runtype-sandbox",
    "language": "python",
    "inference": true,
    "outputVariable": "review",
    "script": "import subprocess\nprint(subprocess.run('opencode run \"Summarize README.md\"', shell=True, capture_output=True, text=True).stdout)"
  }
}
```

Model access ends when the step returns or times out. Runtype bills the inference to the account that runs the Flow. If the script fails, the step follows its error-handling setting, as described in [Handle errors](#handle-errors).

Each agent selects a model with `--model`. The available models are:

* `@cf/zai-org/glm-5.2` (default)
* `@cf/moonshotai/kimi-k2.7-code`
* `@cf/openai/gpt-oss-120b`

Coding agents can need more than the default 5 minutes. Raise the run's step timeout, as described in [Script time limits](#script-time-limits). Set **Network Access** to allow any host the agent needs to read. Model access works under every **Network Access** setting, including **Blocked (default)**.

Other execution environments ignore `inference`. Only **Runtype Sandbox** has the container that these agents run in.

## Handle errors

Transform-data steps continue after a script error by default. The step stores its configured default value in the output variable, or `null` when no default exists, and records the error on the step result.

To change this, open the step's error-handling control and choose one of these options:

* **Continue on error**: Continue the Flow with the default value. This is the default.
* **Stop on error**: Stop the Flow when the step fails.
* **Use fallbacks**: Try the fallbacks that you configure before the step fails.

Wrap operations that can throw in `try`/`catch` when you want to return a structured result:

```javascript
try {
  const parsed = JSON.parse(input.raw_data)
  return { success: true, data: parsed }
} catch (error) {
  return {
    success: false,
    error: error instanceof Error ? error.message : String(error),
  }
}
```

Replace `raw_data` with the variable that contains the JSON string. Use a downstream **Conditional Logic** step to branch on `success`.

To find why a step failed, open the step result in the run's execution logs. For more information, see [Debugging flows](/user-guide/flows/debugging-flows).

## Test a transformation

To test a top-level transform-data step against variables from the last run, follow these steps:

1. Run the Flow once.
2. On the top-level step card, click the action menu (**⋯**).
3. Choose **Test with last run**. The test sheet opens.
4. Review the output and update the script if needed.

## Follow best practices

Use these practices when you write transform-data scripts:

* Keep each transformation focused.
* Name each step and output variable to describe its result.
* Check for `null` and `undefined` before you access properties.
* Return the same structure from each successful run.

## Next steps

Continue with these guides:

* [Flow variables and templates](/user-guide/flows/flow-variables-and-templates): reference inputs and step outputs.
* [Using conditional steps](/user-guide/flows/using-conditional-steps): branch on transformed values.
* [Using fetch-url and api-call steps](/user-guide/flows/using-fetch-url-and-api-call-steps): fetch data before you transform it.
* [Timeouts and long-running work](/user-guide/flows/timeouts-and-long-running-work): set step timeouts for long scripts.
* [Debugging flows](/user-guide/flows/debugging-flows): inspect step results and logs.
* [Flow validation warnings](/user-guide/flows/flow-validation-warnings): resolve warnings about languages and tools on transform-data steps.
* [Manage flows as code](/user-guide/flows/manage-flows-as-code): define transform-data steps with the SDK.