diff --git a/llms-full.txt b/llms-full.txt
index 55526efc..f6fc6aef 100644
--- a/llms-full.txt
+++ b/llms-full.txt
@@ -113101,19 +113101,53 @@ If no slots are available, new steps enter the **waitlist** until resources free
-
-Upstash Workflow does not support per-step level configuration. Meaning that you can attach a flow-control configuration
-to the workflow run and all the steps will inherit to the same limits.
-Following the analogy above, you cannot enforce parallelism limit on "green" steps natively.
+## 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", () => { ... });
+});
+```
-The context.call and context.invoke steps are exception this to this rule and accept their own flow control configuration:
+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.
-* [context.call](/docs/workflow/basics/context/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](/docs/workflow/basics/context/invoke) – starts a new workflow run with its own flow control configuration. This allows the invoked workflow to run under different limits than the parent workflow, giving you more precise control.
+Following the analogy above, this lets you enforce a parallelism limit on the "green" steps only.
-If you want to throttle a specific `context.run` step, the recommended approach is to **extract it into a separate workflow** and call it using `context.invoke()` with its own flow control configuration with a stricter limits.
+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.
+
+
+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.
+`context.call` and `context.invoke` accept their own flow control configuration too:
+
+* [context.call](/docs/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](/docs/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:
@@ -113618,6 +113652,27 @@ const { workflowRunId } = await client.trigger({
})
```
+### Configure Retries for a Single Step
+
+The retry configuration of a run applies to all of its steps. You can override it for a single `context.run`
+step by passing `retries` and `retryDelay` as the third argument:
+
+```typescript Configure Retries for a Single Step
+export const { POST } = serve(async (context) => {
+ await context.run(
+ "call flaky service",
+ () => callFlakyService(),
+ {
+ retries: 5,
+ retryDelay: "1000",
+ }
+ );
+});
+```
+
+Any field left unset falls back to the configuration the run was triggered with.
+The step-level options can be combined with [step-level flow control](/docs/workflow/features/flow-control#step-level-flow-control).
+
# Prevent Retries
Source: https://upstash.com/docs/workflow/features/retries/prevent-retries
@@ -114326,13 +114381,13 @@ These steps accept their own configuration options, allowing fine-grained contro
If not specified, they fall back to their default values.
For details, see:
-* [context.call](/docs/workflow/basics/context/run)
-* [context.invoke](/docs/workflow/basics/context/run)
+* [context.call](/docs/workflow/steps/call)
+* [context.invoke](/docs/workflow/steps/invoke)
-Upstash Workflow does not support step level configuration. The configuration applies to all steps executed by a workflow run.
-
-If you want to specifically throttle a step, there is a workaround by splitting step to another workflow and using `context.invoke()`.
+A single `context.run` step can override the run's flow control and retry configuration by passing
+`flowControl`, `retries` or `retryDelay` as its third argument. See
+[step-level flow control](/docs/workflow/features/flow-control#step-level-flow-control).
# Handle Failed Runs
@@ -117693,6 +117748,19 @@ The context object provides:
Defaults to an empty array when no label was set.
+
+ The flow control configuration QStash applied to the current request: `key`, `parallelism`, `rate` and `period`.
+ Reflects [step-level flow control](/docs/workflow/features/flow-control#step-level-flow-control) when the step sets its own.
+
+
+
+ The retry limit QStash applied to the current request.
+
+
+
+ The retry delay expression QStash applied to the current request.
+
+
Deprecated. Use `labels` instead. When a run has multiple labels, this only
returns the first one.
@@ -118265,6 +118333,29 @@ It returns a `Promise`, so you can decide how steps execute:
The business logic to run inside this step.
+
+ Flow control and retry configuration for this step only. Fields left unset fall back to the
+ configuration the run was triggered with.
+
+ See [step-level flow control](/docs/workflow/features/flow-control#step-level-flow-control) and
+ [retries](/docs/workflow/features/retries#configure-retries-for-a-single-step) for details.
+
+
+
+ Flow control configuration with `key`, `rate`, `parallelism` and `period`, the same shape as in
+ [client.trigger](/docs/workflow/basics/client/trigger). Replaces the run's flow control for this step.
+
+
+
+ How many times the step is retried when it fails.
+
+
+
+ Delay between retries in milliseconds. Can use expressions like `"1000 * (1 + retried)"`.
+
+
+
+
## Response
Each step can return a JSON-serializable value—anything from simple primitives to complex objects.
diff --git a/workflow/features/flow-control.mdx b/workflow/features/flow-control.mdx
index f6501df1..96406926 100644
--- a/workflow/features/flow-control.mdx
+++ b/workflow/features/flow-control.mdx
@@ -84,19 +84,53 @@ If no slots are available, new steps enter the **waitlist** until resources free
-
-Upstash Workflow does not support per-step level configuration. Meaning that you can attach a flow-control configuration
-to the workflow run and all the steps will inherit to the same limits.
-Following the analogy above, you cannot enforce parallelism limit on "green" steps natively.
+## 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.
-The context.call and context.invoke steps are exception this to this rule and accept their own flow control configuration:
+Following the analogy above, this lets you enforce a parallelism limit on the "green" steps only.
-- [context.call](/workflow/basics/context/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/basics/context/invoke) – starts a new workflow run with its own flow control configuration. This allows the invoked workflow to run under different limits than the parent workflow, giving you more precise control.
+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.
-If you want to throttle a specific `context.run` step, the recommended approach is to **extract it into a separate workflow** and call it using `context.invoke()` with its own flow control configuration with a stricter limits.
+
+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.
+`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
diff --git a/workflow/features/retries.mdx b/workflow/features/retries.mdx
index af2a62f9..479a0126 100644
--- a/workflow/features/retries.mdx
+++ b/workflow/features/retries.mdx
@@ -69,3 +69,24 @@ const { workflowRunId } = await client.trigger({
retryDelay: "(1 + retried) * 1000"
})
```
+
+### Configure Retries for a Single Step
+
+The retry configuration of a run applies to all of its steps. You can override it for a single `context.run`
+step by passing `retries` and `retryDelay` as the third argument:
+
+```typescript Configure Retries for a Single Step
+export const { POST } = serve(async (context) => {
+ await context.run(
+ "call flaky service",
+ () => callFlakyService(),
+ {
+ retries: 5,
+ retryDelay: "1000",
+ }
+ );
+});
+```
+
+Any field left unset falls back to the configuration the run was triggered with.
+The step-level options can be combined with [step-level flow control](/workflow/features/flow-control#step-level-flow-control).
diff --git a/workflow/howto/configure.mdx b/workflow/howto/configure.mdx
index 464c8ad9..7234d7d1 100644
--- a/workflow/howto/configure.mdx
+++ b/workflow/howto/configure.mdx
@@ -32,11 +32,11 @@ These steps accept their own configuration options, allowing fine-grained contro
If not specified, they fall back to their default values.
For details, see:
-- [context.call](/workflow/basics/context/run)
-- [context.invoke](/workflow/basics/context/run)
+- [context.call](/workflow/steps/call)
+- [context.invoke](/workflow/steps/invoke)
-Upstash Workflow does not support step level configuration. The configuration applies to all steps executed by a workflow run.
-
-If you want to specifically throttle a step, there is a workaround by splitting step to another workflow and using `context.invoke()`.
-
\ No newline at end of file
+A single `context.run` step can override the run's flow control and retry configuration by passing
+`flowControl`, `retries` or `retryDelay` as its third argument. See
+[step-level flow control](/workflow/features/flow-control#step-level-flow-control).
+
diff --git a/workflow/steps.mdx b/workflow/steps.mdx
index f113ef0a..32711543 100644
--- a/workflow/steps.mdx
+++ b/workflow/steps.mdx
@@ -72,6 +72,19 @@ The context object provides:
Defaults to an empty array when no label was set.
+
+ The flow control configuration QStash applied to the current request: `key`, `parallelism`, `rate` and `period`.
+ Reflects [step-level flow control](/workflow/features/flow-control#step-level-flow-control) when the step sets its own.
+
+
+
+ The retry limit QStash applied to the current request.
+
+
+
+ The retry delay expression QStash applied to the current request.
+
+
Deprecated. Use `labels` instead. When a run has multiple labels, this only
returns the first one.
diff --git a/workflow/steps/run.mdx b/workflow/steps/run.mdx
index 1f99fbfe..2e5f7e3d 100644
--- a/workflow/steps/run.mdx
+++ b/workflow/steps/run.mdx
@@ -18,6 +18,29 @@ It returns a `Promise`, so you can decide how steps execute:
The business logic to run inside this step.
+
+ Flow control and retry configuration for this step only. Fields left unset fall back to the
+ configuration the run was triggered with.
+
+ See [step-level flow control](/workflow/features/flow-control#step-level-flow-control) and
+ [retries](/workflow/features/retries#configure-retries-for-a-single-step) for details.
+
+
+
+ Flow control configuration with `key`, `rate`, `parallelism` and `period`, the same shape as in
+ [client.trigger](/workflow/basics/client/trigger). Replaces the run's flow control for this step.
+
+
+
+ How many times the step is retried when it fails.
+
+
+
+ Delay between retries in milliseconds. Can use expressions like `"1000 * (1 + retried)"`.
+
+
+
+
## Response
Each step can return a JSON-serializable value—anything from simple primitives to complex objects.