A practical field guide from Automation Ace.
The short answer
Cloudflare Workflows run multistep automations durably. You write a class whose run() method calls step.do() for each unit of work, step.sleep() to wait minutes or days, and step.waitForEvent() to pause until something external happens. Cloudflare saves each step's result, retries failed steps with the policy you set, and resumes where it left off, so a failure in step four does not repeat steps one to three.
It is the code equivalent of a long, multi-step Zap with delays and approvals. New to Workers? Start with Cloudflare Workers 101.
Why use Workflows instead of a plain Worker?
- Durability: each step's output is persisted, so retries do not repeat completed work.
- Per-step retries with delays and backoff.
- Long waits: sleep for hours or days without holding a Worker open.
- Human and external events: pause until an approval, payment, or webhook arrives.
- Visibility: inspect each instance's status and steps.
This mirrors what Next Gen Zaps add over classic Zaps, and what classic Zaps approximate with Delay steps and Human in the Loop.
Build your first workflow
1. Configure the workflow binding:
// wrangler.jsonc
{
"name": "onboarding",
"main": "src/index.js",
"compatibility_date": "2026-09-01",
"workflows": [
{ "name": "onboarding-workflow", "binding": "ONBOARDING", "class_name": "OnboardingWorkflow" }
]
}
2. Write the workflow and a trigger:
// src/index.js — a durable onboarding workflow
import { WorkflowEntrypoint } from 'cloudflare:workers';
export class OnboardingWorkflow extends WorkflowEntrypoint {
async run(event, step) {
const { email, name } = event.payload;
// Each step.do result is saved; failed steps retry without re-running finished ones
const contact = await step.do('create CRM contact',
{ retries: { limit: 5, delay: '10 seconds', backoff: 'exponential' } },
async () => {
const res = await fetch('https://api.example-crm.com/v1/contacts', {
method: 'POST',
headers: { Authorization: `Bearer ${this.env.CRM_API_KEY}`, 'Content-Type': 'application/json' },
body: JSON.stringify({ email, name }),
});
if (!res.ok) throw new Error(`CRM ${res.status}`);
return res.json();
});
await step.do('send welcome email', async () => {
await sendEmail(this.env, email, 'Welcome!', `Hi ${name}, welcome aboard.`);
});
await step.sleep('wait three days', '3 days');
// Pause until an external event arrives (or time out)
const signal = await step.waitForEvent('wait for kickoff booked',
{ type: 'kickoff-booked', timeout: '7 days' }).catch(() => null);
await step.do('follow up', async () => {
if (!signal) await sendEmail(this.env, email, 'Book your kickoff', 'Pick a time that works for you.');
});
return { contactId: contact.id, kickoffBooked: Boolean(signal) };
}
}
// A Worker that starts the workflow from a webhook
export default {
async fetch(request, env) {
const payload = await request.json();
const instance = await env.ONBOARDING.create({ params: payload });
return Response.json({ id: instance.id });
},
};
async function sendEmail(env, to, subject, text) { /* call your email provider's API */ }
3. Deploy with npx wrangler deploy, then POST a payload to the Worker URL to start an instance. Check instances in the dashboard or with Wrangler's workflows commands.
Design rules for durable steps
- Make each step idempotent. A step may run more than once when retried; use unique keys so repeats are harmless. See idempotency.
- Return what later steps need from
step.do(); do not rely on variables set outside steps. - Name steps clearly and keep names stable, since names identify saved results.
- Keep side effects inside steps, not between them.
- Set sensible retry limits and let permanent errors fail clearly.
- Handle timeouts on
waitForEventwith a fallback path.
Ways to start a workflow
- From a webhook Worker, as in the example.
- From a Cron Trigger for scheduled batches.
- From an Email Worker when a message arrives.
- From another system, such as a Zap posting to your Worker.
Send events to a waiting instance (for example, when a kickoff is booked) from another Worker using the instance ID.
Good fits for Workflows
- Client onboarding sequences with waits and follow-ups. See client onboarding automation.
- Order fulfillment across several systems.
- Approval flows that wait for a human decision. See the approval workflow guide.
- Large syncs processed in batches with checkpoints. See connecting two apps.
- AI pipelines: extract, classify, review, then act. See Workers AI.
Limits and pricing notes
Workflows are billed on Workers compute and requests plus storage for workflow state, and they are available on the free plan with lower limits. For example, Cloudflare documents shorter instance-state retention on the free plan. Step counts, payload sizes, and concurrency also have limits. Check Cloudflare's Workflows limits and pricing pages before designing large processes.
Frequently asked questions
What are Cloudflare Workflows?
Cloudflare Workflows run durable, multistep automations on Cloudflare. Each step's result is saved, failed steps retry automatically, and workflows can sleep for long periods or wait for external events.
How do I create a Cloudflare Workflow?
Write a class that extends WorkflowEntrypoint with a run(event, step) method using step.do, step.sleep, and step.waitForEvent, register it under workflows in your Wrangler config with a binding, deploy, and start instances with env.BINDING.create().
What is the difference between a Worker and a Workflow?
A Worker handles a single request or event quickly. A Workflow coordinates many steps over minutes or days, persisting progress and retrying failed steps without repeating completed ones.
Can a Cloudflare Workflow wait for a human approval?
Yes. Use step.waitForEvent to pause until an external event arrives, such as an approval sent from another Worker, with a timeout and a fallback path.
Are Cloudflare Workflows free?
Workflows are available on the Workers Free plan with lower limits, and billed on the paid plan by compute, requests, and state storage. Check Cloudflare's current pricing and limits.
Disclaimer: Zapier features, plan availability, and settings can change. Confirm current details in Zapier's help documentation and Cloudflare's developer documentation and pricing pages. Limits and plan features change over time. Code samples are simplified starting points; add your own validation, error handling, and security review before production use before relying on a specific setting. This article may include links to apps, products, or services; some links may be affiliate links, which means Automation Ace may earn a commission at no extra cost to you.