Workflows
Use this skill for ordered, durable, multi-step processes that need retries, checkpointing, sleeps, human/time delays, or progress across failures.
When to use
Use Workflows for:
- Order fulfillment.
- Multi-step imports/exports.
- Billing, provisioning, and cleanup flows.
- AI pipelines that fetch, transform, embed, store, and notify.
- Delayed retries and durable polling.
Prefer alternatives when:
- Work is a simple fire-and-forget task: use Queues.
- Many clients need live shared state: use Durable Objects.
- Logic is purely stateless and immediate: use Workers.
Coding rules
- Put durable side effects inside
step.do().
- Give steps deterministic, stable names.
- Make every step idempotent; assume retries can happen.
- Store large outputs externally in D1/R2/KV and return small IDs from steps.
- Do not depend on mutable module/global state between steps.
- Pass immutable parameters at workflow creation; do not expect event payloads to change after start.
Workflow skeleton
import { WorkflowEntrypoint, WorkflowStep, WorkflowEvent } from "cloudflare:workers";
export interface Env {
ONBOARDING: Workflow;
DB: D1Database;
JOBS: Queue<{ tenantId: string }>;
}
type Params = {
tenantId: string;
requestedBy: string;
};
export class OnboardingWorkflow extends WorkflowEntrypoint<Env, Params> {
async run(event: WorkflowEvent<Params>, step: WorkflowStep) {
const tenant = await step.do("create tenant record", {
retries: { limit: 3, delay: "5 seconds", backoff: "exponential" }
}, async () => {
return this.env.DB.prepare(
"INSERT INTO tenants (id, requested_by) VALUES (?, ?) ON CONFLICT(id) DO NOTHING RETURNING id"
).bind(event.payload.tenantId, event.payload.requestedBy).first<{ id: string }>();
});
await step.do("enqueue provisioning", async () => {
await this.env.JOBS.send({ tenantId: event.payload.tenantId });
});
await step.sleep("wait before verification", "30 seconds");
return { ok: true, tenantId: event.payload.tenantId, tenant };
}
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const params = await request.json<Params>();
const instance = await env.ONBOARDING.create({
id: `tenant-${params.tenantId}`,
params
});
return Response.json({ id: instance.id });
}
} satisfies ExportedHandler<Env>;
Wrangler binding
{
"workflows": [
{
"name": "onboarding",
"binding": "ONBOARDING",
"class_name": "OnboardingWorkflow"
}
]
}
Idempotency examples
- Use natural primary keys such as
tenantId, orderId, or importId.
- Use
INSERT ... ON CONFLICT DO NOTHING or a processed-events table.
- Use external API idempotency keys when calling payment, email, or provisioning systems.
- Store progress records so manual support can inspect workflow state.
Anti-patterns
- One giant step that hides all failure points.
- Non-deterministic step names based on timestamps/random IDs.
- Returning large blobs from steps instead of R2/D1 references.
- Using Workflows to hold live WebSocket room state.
1---2name: workflows3description: Implement Cloudflare Workflows for durable execution, ordered multi-step jobs, retries, checkpointing, sleeps, long-running processes, imports, billing flows, AI pipelines, and sagas. Use when a Worker needs reliable progress over multiple steps instead of manual retry state.4---5# Workflows67Use this skill for ordered, durable, multi-step processes that need retries, checkpointing, sleeps, human/time delays, or progress across failures.89## When to use1011Use Workflows for:1213- Order fulfillment.14- Multi-step imports/exports.15- Billing, provisioning, and cleanup flows.16- AI pipelines that fetch, transform, embed, store, and notify.17- Delayed retries and durable polling.1819Prefer alternatives when:2021- Work is a simple fire-and-forget task: use Queues.22- Many clients need live shared state: use Durable Objects.23- Logic is purely stateless and immediate: use Workers.2425## Coding rules2627- Put durable side effects inside `step.do()`.28- Give steps deterministic, stable names.29- Make every step idempotent; assume retries can happen.30- Store large outputs externally in D1/R2/KV and return small IDs from steps.31- Do not depend on mutable module/global state between steps.32- Pass immutable parameters at workflow creation; do not expect event payloads to change after start.3334## Workflow skeleton3536```ts37import { WorkflowEntrypoint, WorkflowStep, WorkflowEvent } from "cloudflare:workers";3839export interface Env {40 ONBOARDING: Workflow;41 DB: D1Database;42 JOBS: Queue<{ tenantId: string }>;43}4445type Params = {46 tenantId: string;47 requestedBy: string;48};4950export class OnboardingWorkflow extends WorkflowEntrypoint<Env, Params> {51 async run(event: WorkflowEvent<Params>, step: WorkflowStep) {52 const tenant = await step.do("create tenant record", {53 retries: { limit: 3, delay: "5 seconds", backoff: "exponential" }54 }, async () => {55 return this.env.DB.prepare(56 "INSERT INTO tenants (id, requested_by) VALUES (?, ?) ON CONFLICT(id) DO NOTHING RETURNING id"57 ).bind(event.payload.tenantId, event.payload.requestedBy).first<{ id: string }>();58 });5960 await step.do("enqueue provisioning", async () => {61 await this.env.JOBS.send({ tenantId: event.payload.tenantId });62 });6364 await step.sleep("wait before verification", "30 seconds");6566 return { ok: true, tenantId: event.payload.tenantId, tenant };67 }68}6970export default {71 async fetch(request: Request, env: Env): Promise<Response> {72 const params = await request.json<Params>();73 const instance = await env.ONBOARDING.create({74 id: `tenant-${params.tenantId}`,75 params76 });77 return Response.json({ id: instance.id });78 }79} satisfies ExportedHandler<Env>;80```8182## Wrangler binding8384```jsonc85{86 "workflows": [87 {88 "name": "onboarding",89 "binding": "ONBOARDING",90 "class_name": "OnboardingWorkflow"91 }92 ]93}94```9596## Idempotency examples9798- Use natural primary keys such as `tenantId`, `orderId`, or `importId`.99- Use `INSERT ... ON CONFLICT DO NOTHING` or a processed-events table.100- Use external API idempotency keys when calling payment, email, or provisioning systems.101- Store progress records so manual support can inspect workflow state.102103## Anti-patterns104105- One giant step that hides all failure points.106- Non-deterministic step names based on timestamps/random IDs.107- Returning large blobs from steps instead of R2/D1 references.108- Using Workflows to hold live WebSocket room state.