Durable execution with step.do(). Build multi-step agent flows, payment pipelines, and long-running orchestrations without writing your own retry logic or state machine.
Workflows is Cloudflare's durable execution engine. You define a sequence of steps using step.do(), and the platform guarantees that each step runs exactly once, retries on failure, and persists its result. If the process crashes mid-way, it resumes from the last completed step — not from the beginning.
Think of it as a lightweight alternative to AWS Step Functions or Temporal, but running on Cloudflare's edge with no infrastructure to manage. Each workflow instance is backed by a Durable Object, so state is strongly consistent and the execution model is single-writer.
The primary use case is agent orchestration: multi-step LLM calls, tool invocations, and external API calls that need to survive failures without re-running expensive steps.
A workflow is a class with a run() method. Inside it, you call step.do() for each durable step. The name is the checkpoint key — if the workflow restarts, completed steps are skipped and their cached results returned.
# wrangler.toml name = "order-processor" main = "src/index.ts" [[workflows]] name = "order-flow" binding = "ORDER_FLOW" class_name = "OrderWorkflow"
Each step.do() call is an atomic unit. If it throws, the workflow retries it with configurable backoff. step.sleep() pauses the workflow for a duration without consuming compute.
import { WorkflowEntrypoint } from "cloudflare:workers"; export class OrderWorkflow extends WorkflowEntrypoint { async run(event, step) { const validated = await step.do("validate", async () => { return validateOrder(event.payload); }); const payment = await step.do("charge", async () => { return chargeCard(validated); }); await step.sleep("wait-for-fraud-check", "30 seconds"); await step.do("fulfil", async () => { return shipOrder(payment.orderId); }); } }
Start a workflow instance from any Worker using the binding. Each instance gets a unique ID and can be queried for status.
export default { async fetch(req, env) { const order = await req.json(); const instance = await env.ORDER_FLOW.create({ params: order, }); return Response.json({ id: instance.id }); } };
If two steps share a name, the second returns the cached result of the first. This is a feature for idempotency but a bug if you didn't mean it. Use descriptive, unique step names.
Whatever your step function returns gets persisted. Classes, functions, and Buffers won't survive the round trip. Return plain objects.
Only code inside a step.do() callback is guaranteed exactly-once. Anything between steps runs again on replay. Put all side effects inside steps.
Workflows are backed by Durable Objects with single-writer semantics. You can run many instances of the same workflow class, but each instance ID is serialised.