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

# forEach

> Iterate over rows from a previous step with parallelism and rate limiting

`forEach` lets a service step execute once per row in a source table. The workflow engine handles iteration, parallelization, rate limiting, and per-item error handling — you define the source and map row values into parameters.

## Basic usage

Given a source table `repos` with a list of repositories, this step fetches commits for each repo:

```typescript theme={null}
{
  service: "github",
  operation: "listCommits",
  params: {
    owner: "{{item.owner}}",
    repo: "{{item.name}}",
  },
  outputTable: "raw_commits",
  outputSchema: {
    sha: "VARCHAR",
    message: "VARCHAR",
    author: "VARCHAR",
    repo_name: "VARCHAR",
  },
  forEach: {
    source: "repos",
    itemKey: "id",
  },
}
```

The engine iterates over every row in `repos`, substituting `{{item.owner}}` and `{{item.name}}` with column values from each row.

## Configuration

| Field          | Default  | Description                                                    |
| -------------- | -------- | -------------------------------------------------------------- |
| `source`       | —        | Table from a previous step to iterate over                     |
| `itemKey`      | —        | Column used as the unique identifier for each item             |
| `parallel`     | `1`      | Maximum concurrent calls (up to `50`)                          |
| `rateLimit`    | —        | Throttle requests per second or minute                         |
| `runAsUser`    | —        | Template for per-user credential impersonation                 |
| `maxRetries`   | `3`      | Retry attempts per item on failure                             |
| `retryDelayMs` | `1000`   | Base delay with exponential backoff                            |
| `onItemError`  | `"fail"` | `"fail"` stops the step, `"skip"` continues to remaining items |

## Template variables

Use `{{item.X}}` to reference columns from the source table row:

```typescript theme={null}
params: {
  parentId: "{{item.id}}",
  owner: "{{item.owner_id}}",
  since: "{{var.startDate}}",
}
```

`{{var.X}}` references workflow-level [variables](/docs/platform-docs/workflows-framework/variables). Both can be used together in the same step.

## Parallelism and rate limiting

To process multiple accounts concurrently while staying within API rate limits:

```typescript theme={null}
forEach: {
  source: "accounts",
  itemKey: "id",
  parallel: 10,
  rateLimit: {
    requestsPerSecond: 5,
  },
}
```

## Error handling

By default, a single item failure stops the entire step. To continue processing remaining items when individual items fail:

```typescript theme={null}
forEach: {
  source: "records",
  itemKey: "id",
  onItemError: "skip",
  maxRetries: 3,
  retryDelayMs: 1000,
}
```

With `"skip"`, failed items are logged but don't block the rest. The output table contains results from all successful items.

## Multi-user workflows

Use `runAsUser` to execute each iteration with a different user's credentials. This is the pattern for system-level workflows that aggregate data across multiple users that have different credentials configured for a service.

Given a `users` table where each user has configured their Slack credentials, this step fetches all channels for each user using their individual credentials:

```typescript theme={null}
{
  service: "slack",
  operation: "listChannels",
  outputTable: "all_channels",
  outputSchema: {
    id: "VARCHAR",
    name: "VARCHAR",
    user_id: "VARCHAR",
  },
  forEach: {
    source: "users",
    itemKey: "id",
    runAsUser: "{{item.credential_user_id}}",
    onItemError: "skip",
    parallel: 5,
  },
  paginate: {
    type: "cursor",
    cursorField: "response_metadata.next_cursor",
    cursorParam: "cursor",
  },
}
```

Each iteration uses the credentials of the user specified by `credential_user_id`. If one user's credentials are invalid, `onItemError: "skip"` ensures other users are still processed.

Note `forEach` can be combined with [pagination](/docs/platform-docs/workflows-framework/pagination) — each forEach iteration paginates independently through all of the service call's results.

## Output

All iterations append results to a single output table just like any other workflow step result.

## Validation

| Check                                                             |
| ----------------------------------------------------------------- |
| `source` table exists in previous steps                           |
| `{{item.X}}` references exist in the source table's output schema |
