Skip to main content
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:
{
  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

FieldDefaultDescription
sourceTable from a previous step to iterate over
itemKeyColumn used as the unique identifier for each item
parallel1Maximum concurrent calls (up to 50)
rateLimitThrottle requests per second or minute
runAsUserTemplate for per-user credential impersonation
maxRetries3Retry attempts per item on failure
retryDelayMs1000Base 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:
params: {
  parentId: "{{item.id}}",
  owner: "{{item.owner_id}}",
  since: "{{var.startDate}}",
}
{{var.X}} references workflow-level 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:
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:
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:
{
  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 — 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