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

# Dynamic Steps

> Run a variable number of steps based on runtime data

Sometimes you don't know how many steps a workflow needs until it runs. You
might need to fetch data for each item in a list, process rows from a query,
or fan out across a set of IDs from an API response.

OpenWorkflow handles this automatically. When multiple steps share the same
name, they're disambiguated in order (`fetch-data`, `fetch-data:1`,
`fetch-data:2`, ...). You don't need to generate unique names yourself.

## Basic Pattern

Map over your data and create a step per item using `Promise.all`:

```ts theme={null}
const results = await Promise.all(
  input.items.map((item) =>
    step.run({ name: "fetch-data" }, async () => {
      return await thirdPartyApi.fetch(item.id);
    }),
  ),
);
```

Every step uses the same name — OpenWorkflow appends `:1`, `:2`, etc.
automatically. Each step is individually memoized, so if the workflow restarts,
completed steps return their cached results and only the remaining steps
re-execute.

## Stable IDs for Mutable Collections

If items can be added, removed, or reordered between retries, include a stable
ID from the data in the step name instead of relying on auto-indexing:

```ts theme={null}
const results = await Promise.all(
  input.orders.map((order) =>
    step.run({ name: `process-order:${order.id}` }, async () => {
      return await processOrder(order);
    }),
  ),
);
```

This way, each step is tied to a specific item regardless of its position in
the array. Use any stable identifier — a database ID, a slug, or a unique key
from the data itself.
