> ## Documentation index
> Fetch the complete documentation index at: https://upstash.com/docs/llms.txt
> Search it with GET https://upstash.com/docs/search?q=<query>.
> Use these to discover all available pages before exploring further.

# Overview

Flow Control allows you to limit how many workflow steps are executed by delaying and queuing their delivery.

This feature helps to:
- Manage resource consumption
- Prevent violations of external API rate limits
- Ensure workflows run within defined system constraints

## How Flow Control Works

When defined limits are exceeded, Flow Control automatically queues and delays step executions instead of rejecting them.
This guarantees that all steps are eventually processed while staying within configured thresholds.

To configure Flow Control, you define a flow control key, a unique identifier used to group related steps under the same rate and parallelism limits.
The steps that has the same flow control key respect the same constraints.

There are two main parameters to configure:

- [Rate and Period](/workflow/features/flow-control/rate-period): Maximum number of steps that may start within a time window
- [Parallelism](/workflow/features/flow-control/parallelism): Maximum number of steps allowed to run concurrently

These parameters can be combined for fine‑grained control.
For example, you can allow up to 10 steps per minute but restrict concurrency
to 5 steps in parallel, ensuring more predictable load patterns.

## Example

Suppose you have the following workflow:

```typescript
export const { POST } = serve<{ topic: string }>(async (context) => {
  const payload = context.requestPayload

  await context.run("step-1", () => { ... });

  await context.run("step-2", () => { ... });

  await context.run("step-3", () => { ... });
})
```

Now imagine you trigger **N workflow runs** for this workflow with the following configuration:

```typescript
const { workflowRunId } = await client.trigger({
  url: "https://<YOUR_WORKFLOW_ENDPOINT>/<YOUR-WORKFLOW-ROUTE>",
  flowControl: {
    key: "fw_example",
    parallelism: 7,
    rate: 3,
    period: "1m",
  }
})
```

Without Flow Control, all workflow runs immediately execute their steps as soon as possible.
If the workflow calls an external API in a step, this would likely result in ~N concurrent requests being fired in a very short timeframe, potentially overloading services or breaching API limits.

<Frame caption="Workflow simplified by step types">
  <img src="/img/workflow/flow_control_ex_3.png" alt="Flow-controlled workflow step types" />
</Frame>

With the configuration above:
- **Rate:** At most 3 steps per minute can start across all workflow runs.
- **Parallelism:** At most 7 steps can be running at the same time.

Steps that exceed these limits are automatically queued and executed later.

<Frame caption="Steps are enqueued for execution">
  <img src="/img/workflow/flow_control_ex_2.png" alt="Workflow steps queued for execution" />
</Frame>

Note that each step above corresponds to a separate workflow run.
Because this workflow is sequential, each workflow run has only one pending step at a time.
In workflows with **parallel branches**, multiple steps from the same workflow run may appear in the schedule simultaneously.

Parallelism slots are consumed by running steps.
If no slots are available, new steps enter the **waitlist** until resources free up:

<Frame caption="Parallelism waitlist for the flow-control">
  <img src="/img/workflow/flow_control_ex_1.png" alt="Workflow parallelism waitlist" />
</Frame>

## Step-level Flow Control

The flow control configuration of a run applies to all of its steps. To limit a single `context.run` step more
strictly, pass a flow control configuration as the third argument of `context.run`:

```typescript
export const { POST } = serve(async (context) => {
  await context.run("prepare", () => { ... });

  await context.run(
    "charge customer",
    () => chargeCustomer(),
    {
      flowControl: { key: "payment-provider", parallelism: 3 },
    }
  );

  await context.run("notify", () => { ... });
});
```

Only the `charge customer` step is bound by the `payment-provider` key. The other steps keep the configuration
the run was triggered with. Since the key is shared, at most 3 `charge customer` steps run at the same time
across all runs using that key, no matter how many runs are active.

Following the analogy above, this lets you enforce a parallelism limit on the "green" steps only.

The step-level configuration replaces the run's flow control for that step as a whole: a step with its own
flow control configuration does not also count against the run's flow control key.

<Note>
When the step with flow control is reached right after a `context.run`, `context.sleep` or
`context.sleepUntil` step, its configuration is attached to the request which delivers it, at no extra cost.
In other cases (for example, when it is the first step of a run or comes after a `context.call`),
the SDK publishes an additional hidden request to apply the configuration. This request is not shown in the
workflow logs.

Steps in a parallel group carry their own configuration and never need the extra request.
</Note>

`context.call` and `context.invoke` accept their own flow control configuration too:

- [context.call](/workflow/steps/call) – lets you run external HTTP requests under a separate key, so you can throttle third‑party API calls independently of your workflow logic.
- [context.invoke](/workflow/steps/invoke) – starts a new workflow run with its own flow control configuration.

Inside a step, `context.flowControl` returns the flow control configuration applied to the current delivery.

## Configuration

You can configure flow control when starting a workflow run:

```typescript Configure Retry Attempt Count
import { Client } from "@upstash/workflow";

const client = new Client({ token: "<QSTASH_TOKEN>" })

const { workflowRunId } = await client.trigger({
  url: "https://<YOUR_WORKFLOW_ENDPOINT>/<YOUR-WORKFLOW-ROUTE>",
  flowControl: {
    key: "user-signup",
    parallelism: 1,
    rate: 10,
    period: 100,
  }
})
```

All steps within a workflow run will adhere to the specified flow control configuration.

<Warning>
Keep in mind that rate/period and parallelism info are kept on each step separately.
If you change the rate/period or parallelism on a new deployment, the old fired ones will not be affected.
They will keep their flow control configuration.

During the period that old steps have not been delivered but there are also steps with new rates, Upstash Workflow will effectively allow the highest rate/period or highest parallelism. Eventually (after the old publishes are delivered), the new rate/period and parallelism will be used.
</Warning>
