Workers API (5)
Last updated Oct 12, 2026 This guide details the Workflows API within Cloudflare Workers, including methods, types, and usage examples.
WorkflowEntrypoint
The WorkflowEntrypoint class is the core element of a Workflow definition. A Workflow must extend this class and define a run method with at least one step call to be considered a valid Workflow.
export class MyWorkflow extends WorkflowEntrypoint<Env, Params> {
async run(event: WorkflowEvent<Params>, step: WorkflowStep) {
// Steps here
}
}
run
run(event: WorkflowEvent<T>, step: WorkflowStep): Promise<T>event- the event passed to the Workflow, including an optionalpayloadcontaining data (parameters)step- theWorkflowSteptype that provides the step methods for your Workflow
The run method can optionally return data, which is available when querying the instance status via the Workers API, REST API and the Workflows dashboard. This can be useful if your Workflow is computing a result, returning the key to data stored in object storage, or generating some kind of identifier you need to act on.
export class MyWorkflow extends WorkflowEntrypoint<Env, Params> {
async run(event: WorkflowEvent<Params>, step: WorkflowStep) {
// Steps here
let someComputedState = await step.do("my step", async () => {});
// Optional: return state from our run() method
return someComputedState;
}
}
The WorkflowEvent type accepts an optional type parameter ↗︎ that allows you to provide a type for the payload property within the WorkflowEvent.
Refer to the events and parameters documentation for how to handle events within your Workflow code.
Finally, any JS control-flow primitive (if conditions, loops, try...catch blocks, promises, and more) can be used to manage steps inside the run method.
WorkflowEvent
export type WorkflowCronSchedule = {
/** Cron expression that triggered this event. */
cron: string;
/** Timestamp of the scheduled trigger, in milliseconds since the Unix epoch. */
scheduledTime: number;
};
export type WorkflowEvent<T> = {
payload: Readonly<T>;
timestamp: Date;
instanceId: string;
workflowName: string;
schedule?: WorkflowCronSchedule;
};
- The
WorkflowEventis the first argument to a Workflow'srunmethod. payload- a default type ofanyor typeTif a type parameter is provided.timestamp- aDateobject set to the time the Workflow instance was created (triggered).instanceId- the ID of the associated instance.workflowName- the name of the associated Workflow.schedule- metadata for Workflow instances created by a cron schedule, including thecronexpression andscheduledTimein milliseconds since the Unix epoch.
Refer to the events and parameters documentation for how to handle events within your Workflow code.
WorkflowStep
step
step.do(name: string, callback: (ctx: WorkflowStepContext): RpcSerializable): Promise<T>step.do(name: string, callback: (ctx: WorkflowStepContext): RpcSerializable, rollbackOptions?: WorkflowStepRollbackOptions<T>): Promise<T>step.do(name: string, config?: WorkflowStepConfig, callback: (ctx: WorkflowStepContext): RpcSerializable): Promise<T>name- the name of the step, up to 256 characters.config(optional) - an optionalWorkflowStepConfigfor configuring step specific retry behaviour.callback- an asynchronous function that receives aWorkflowStepContextand optionally returns serializable state for the Workflow to persist. In JavaScript Workflows, this includes a fresh, unlockedReadableStream<Uint8Array>for large binary output.step.do(name: string, config?: WorkflowStepConfig, callback: (ctx: WorkflowStepContext): RpcSerializable, rollbackOptions?: WorkflowStepRollbackOptions<T>): Promise<T>name- the name of the step, up to 256 characters.config(optional) - an optionalWorkflowStepConfigfor configuring step specific retry behaviour.callback- an asynchronous function that receives aWorkflowStepContextand optionally returns serializable state for the Workflow to persist. In JavaScript Workflows, this includes a fresh, unlockedReadableStream<Uint8Array>for large binary output.rollbackOptions(optional) - register rollback logic for the step. If the Workflow later fails, registered rollbacks run in reverse step-start order.
Returning state
When returning state from a step, ensure that the object you return is serializable.
Primitive types like string, number, and boolean, along with composite structures such as Array and Object (provided they only contain serializable values), can be serialized. Any structured-cloneable ↗︎ type can be serialized, as long it is no longer than 1 MB.
On the other hand, objects that include Function or Symbol types, and objects with circular references, cannot be serialized. The Workflow instance will throw an error if objects with those types is returned.
In JavaScript Workflows, ReadableStream<Uint8Array> is a supported serializable return type when a step needs to persist larger binary output than the normal 1 MiB non-stream step-result limit.
Return a new stream from the callback.
Caution
Do not return a locked stream or a stream that has already been read. BYOB streams and BYOB readers are not supported.
After a ReadableStream<Uint8Array> object has been persisted within a step, it should not be reused - rely on the new fresh stream that gets returned from step. The bytes are preserved from the original stream, but the implementation might differ.
export class MyWorkflow extends WorkflowEntrypoint {
async run(_event, step) {
const reportStream = await step.do("read report from R2", async () => {
const object = await this.env.MY_BUCKET.get("reports/latest.csv");
if (!object?.body) {
throw new Error("Could not read reports/latest.csv from R2.");
}
return object.body;
});
const preview = await new Response(reportStream).text();
return { preview };
}
}
type Env = {
MY_BUCKET: R2Bucket;
};
export class MyWorkflow extends WorkflowEntrypoint<Env> {
async run(_event: WorkflowEvent<unknown>, step: WorkflowStep) {
const reportStream = await step.do("read report from R2", async () => {
const object = await this.env.MY_BUCKET.get("reports/latest.csv");
if (!object?.body) {
throw new Error("Could not read reports/latest.csv from R2.");
}
return object.body;
});
const preview = await new Response(reportStream).text();
return { preview };
}
}
step.sleep(name: string, duration: WorkflowDuration): Promise<void>name- the name of the step.duration- the duration to sleep for, as anumberin milliseconds or as aWorkflowDuration-compatible string.- Refer to the documentation on sleeping and retrying to learn more about how Workflows are retried.
step.sleepUntil(name: string, timestamp: Date | number): Promise<void>name- the name of the step.timestamp- a JavaScriptDateobject or milliseconds from the Unix epoch to sleep the Workflow instance until.
Note
step.sleep and step.sleepUntil methods do not count towards the maximum Workflow steps limit.
More information about the limits imposed on Workflow can be found in the Workflows limits documentation.
step.waitForEvent(name: string, options: ): Promise<void>-name- the name of the step. -options- an object with properties fortype(up to 100 characters <sup>1</sup>), which determines which event type thiswaitForEventcall will match on when callinginstance.sendEvent, and an optionaltimeoutproperty, which defines how long thewaitForEventcall will block for before throwing a timeout exception. The default timeout is 24 hours.
export class MyWorkflow extends WorkflowEntrypoint {
async run(event, step) {
// Other steps in your Workflow
let stripeEvent = await step.waitForEvent(
"receive invoice paid webhook from Stripe",
{ type: "stripe-webhook", timeout: "1 hour" },
);
// Rest of your Workflow
}
}
export class MyWorkflow extends WorkflowEntrypoint<Env, Params> {
async run(event: WorkflowEvent<Params>, step: WorkflowStep) {
// Other steps in your Workflow
let stripeEvent = await step.waitForEvent<IncomingStripeWebhook>(
"receive invoice paid webhook from Stripe",
{ type: "stripe-webhook", timeout: "1 hour" },
);
// Rest of your Workflow
}
}
Review the documentation on events and parameters to learn how to send events to a running Workflow instance.
WorkflowStepConfig
export type WorkflowDynamicDelayContext = {
ctx: WorkflowStepContext;
error: Error;
};
export type WorkflowDelayFunction = (
input: WorkflowDynamicDelayContext,
) => string | number | Promise<string | number>;
export type WorkflowStepConfig = {
retries?: {
limit: number;
delay: string | number | WorkflowDelayFunction;
backoff?: WorkflowBackoff;
};
timeout?: string | number;
};
- A
WorkflowStepConfigis an optional argument to thedomethod of aWorkflowStepand defines properties that allow you to configure the retry behaviour of that step. - Set
retries.delayto a fixed duration, or pass aWorkflowDelayFunctionto calculate the next retry delay from the current step context and thrown error.
Refer to the documentation on sleeping and retrying to learn more about how Workflows are retried.
Rollback options
type WorkflowRollbackContext<T = unknown> = {
ctx: WorkflowStepContext;
error: Error;
output: T | undefined;
};
type WorkflowRollbackHandler<T = unknown> = (
ctx: WorkflowRollbackContext<T>,
) => Promise<void>;
type WorkflowStepRollbackConfig = Pick<
WorkflowStepConfig,
"retries" | "timeout"
>;
type WorkflowStepRollbackOptions<T = unknown> = {
rollback: WorkflowRollbackHandler<T>;
rollbackConfig?: WorkflowStepRollbackConfig;
};
- Pass this
WorkflowStepRollbackOptionsobject as the final argument tostep.do()to register a compensating action for a successful step. rollbackreceives the original step context, the error that caused the Workflow to fail, and the step output returned by the forward step.rollbackConfigapplies retry and timeout settings to the rollback handler itself.
export class BillingWorkflow extends WorkflowEntrypoint {
async run(_event, step) {
await step.do(
"create charge",
async () => {
const charge = await createCharge();
return { chargeId: charge.id };
},
{
rollback: async ({ ctx, output, error }) => {
const { chargeId } = output;
await refundCharge(chargeId, {
reason: `${ctx.step.name}: ${error.message}`,
});
},
rollbackConfig: {
retries: {
limit: 3,
delay: "30 seconds",
backoff: "linear",
},
timeout: "5 minutes",
},
},
);
}
}
export class BillingWorkflow extends WorkflowEntrypoint<Env> {
async run(_event: WorkflowEvent<unknown>, step: WorkflowStep) {
await step.do(
"create charge",
async () => {
const charge = await createCharge();
return { chargeId: charge.id };
},
{
rollback: async ({ ctx, output, error }) => {
const { chargeId } = output as { chargeId: string };
await refundCharge(chargeId, {
reason: `${ctx.step.name}: ${error.message}`,
});
},
rollbackConfig: {
retries: {
limit: 3,
delay: "30 seconds",
backoff: "linear",
},
timeout: "5 minutes",
},
},
);
}
}
WorkflowStepContext
export type WorkflowStepContext = {
step: {
name: string;
count: number;
};
attempt: number;
config: WorkflowStepConfig;
};
- The
WorkflowStepContextis passed as the first argument to thestep.docallback function. It provides runtime information about the current step. step.name- the name of the step as passed tostep.do.step.count- how many timesstep.dohas been called with this name in the current Workflow run (1-indexed).attempt- the current attempt number (1-indexed).1on the first try,2on the first retry, and so on.config- the resolvedWorkflowStepConfigfor this step, including any defaults applied by the runtime.
Refer to the step context documentation for usage examples.
Workflow step limits
Each workflow on Workers Paid supports 10,000 steps by default. You can increase this up to 25,000 steps by configuring steps within the limits property of your Workflow definition in your Wrangler configuration:
{
"$schema": "./node_modules/wrangler/config-schema.json",
"workflows": [
{
"name": "my-workflow",
"binding": "MY_WORKFLOW",
"class_name": "MyWorkflow",
"limits": {
"steps": 25000
}
}
]
}
[[workflows]]
name = "my-workflow"
binding = "MY_WORKFLOW"
class_name = "MyWorkflow"
[workflows.limits]
steps = 25_000
step.sleep does not count towards the maximum steps limit.
Note that Workflows on Workers Free have a limit of 1,024 steps. Refer to Workflow limits for more information.
Default instance retention
By default, the state of a finished Workflow instance is retained for the maximum retention period available on your account. To retain instances for a shorter period, configure default_retention on the Workflow binding in your Wrangler configuration:
{
"workflows": [
{
"name": "my-workflow",
"binding": "MY_WORKFLOW",
"class_name": "MyWorkflow",
"default_retention": {
"success_retention": "3 days",
"error_retention": "7 days",
},
},
],
}
[[workflows]]
name = "my-workflow"
binding = "MY_WORKFLOW"
class_name = "MyWorkflow"
[workflows.default_retention]
success_retention = "3 days"
error_retention = "7 days"
success_retentionapplies to instances that complete successfully and to instances that are terminated, anderror_retentionapplies to instances that end in an errored state.- Both fields are optional and accept either a duration string, such as
"3 days", or a whole number of milliseconds.
Instance-level retention settings take precedence over Workflow binding settings, which take precedence over the account default. The retention period cannot exceed your account's retention limit.
Note
default_retention cannot be combined with script_name. If you bind to a Workflow defined in another Worker, set default_retention on the Worker that defines the Workflow.
Declare Workflows in exports
Note
Requires Wrangler 4.139.0 or above.
You can declare the Workflows a Worker defines in the exports field of your Wrangler configuration, instead of in a workflows binding:
- The Worker that declares the Workflow calls it through
ctx.exports. - Other Workers call the Workflow through a cross-script binding. Set
script_nameto the Worker that declares the export, andclass_nameto the key of the export.
