Cloudflare Workflows
Status: Production Ready ✅ | Last Verified: 2025-12-27 | Version: 3.0.0
Dependencies: cloudflare-worker-base (for Worker setup)
Contents: Quick Start • Commands • Agents • Core Concepts • Critical Rules • Top Errors • Common Patterns • When to Load References • Limits
Quick Start (10 Minutes)
1. Create a Workflow
Use the Cloudflare Workflows starter template:
npm create cloudflare@latest my-workflow -- --template cloudflare/workflows-starter --git --deploy false
cd my-workflow
What you get:
- WorkflowEntrypoint class template
- Worker to trigger workflows
- Complete wrangler.jsonc configuration
2. Basic Workflow Structure
src/index.ts:
import { WorkflowEntrypoint, WorkflowStep, WorkflowEvent } from 'cloudflare:workers';
type Env = {
MY_WORKFLOW: Workflow;
};
type Params = {
userId: string;
email: string;
};
export class MyWorkflow extends WorkflowEntrypoint<Env, Params> {
async run(event: WorkflowEvent<Params>, step: WorkflowStep) {
const { userId, email } = event.payload;
// Step 1: Do work with automatic retries
const result = await step.do('process user', async () => {
return { processed: true, userId };
});
// Step 2: Wait before next step
await step.sleep('wait 1 hour', '1 hour');
// Step 3: Continue workflow
await step.do('send email', async () => {
return { sent: true, email };
});
return { completed: true, userId };
}
}
// Worker to trigger workflow
export default {
async fetch(req: Request, env: Env): Promise<Response> {
const instance = await env.MY_WORKFLOW.create({
params: { userId: '123', email: 'user@example.com' }
});
return Response.json({
id: instance.id,
status: await instance.status()
});
}
};
Template: See templates/basic-workflow.ts for complete example
3. Configure wrangler.jsonc
{
"name": "my-workflow",
"main": "src/index.ts",
"compatibility_date": "2025-10-22",
"workflows": [
{
"binding": "MY_WORKFLOW",
"name": "my-workflow",
"class_name": "MyWorkflow"
}
]
}
Template: See templates/wrangler-workflows-config.jsonc
4. Deploy
npm run deploy
Commands
Interactive slash commands for workflow development:
| Command | Description | Use When |
|---|---|---|
/workflow-setup | Complete wizard for new workflow projects | Starting new project, need full setup |
/workflow-create | Quick scaffolding for workflow classes | Adding workflow to existing project |
/workflow-debug | Interactive debugging with error patterns | Troubleshooting workflow issues |
/workflow-test | Test workflows locally and remotely | Validating workflow behavior |
Example Usage:
/workflow-setup # Full guided setup wizard
/workflow-create # Quick workflow scaffolding
/workflow-debug # Debug workflow issues
/workflow-test # Test workflow execution
Agents
Autonomous agents for complex workflow tasks:
| Agent | Description | Triggers |
|---|---|---|
workflow-debugger | Auto-detects and fixes configuration/runtime errors | "debug workflow", "fix workflow errors" |
workflow-optimizer | Analyzes performance, cost, and reliability | "optimize workflow", "improve performance" |
workflow-setup-assistant | Autonomous project scaffolding | "setup workflow", "create first workflow" |
Key Capabilities:
- Debugger: 6-phase analysis, auto-fix for I/O context, serialization, export issues
- Optimizer: Cost analysis, reliability scoring, actionable recommendations
- Setup Assistant: Project detection, automatic scaffolding, validation
Scripts
Automation scripts in scripts/ directory:
| Script | Purpose |
|---|---|
validate-workflow-config.sh | Validate wrangler.jsonc configuration |
test-workflow.sh | Create and test workflow instances |
benchmark-workflow.sh | Measure performance and cost |
generate-workflow.sh | Scaffold new workflows from templates |
check-workflow-limits.sh | Validate against Cloudflare limits |
Usage:
./scripts/validate-workflow-config.sh # Check config
./scripts/test-workflow.sh my-workflow # Test workflow
./scripts/benchmark-workflow.sh my-workflow 10 # Benchmark 10 runs
./scripts/generate-workflow.sh MyWorkflow # Generate scaffold
./scripts/check-workflow-limits.sh src/workflows/my-workflow.ts
Core Concepts
WorkflowEntrypoint
Every workflow must extend WorkflowEntrypoint:
export class MyWorkflow extends WorkflowEntrypoint<Env, Params> {
async run(event: WorkflowEvent<Params>, step: WorkflowStep) {
// Workflow logic here
}
}
Key Points:
Env: Environment bindings (KV, D1, etc.)Params: Typed payload passed when creating workflow instanceevent: Containsid,payload,timestampstep: Methods for durable execution
Step Methods
All workflow work MUST be done in steps for durability:
// step.do - Execute work with automatic retries
await step.do('step name', async () => {
return { result: 'data' };
});
// step.sleep - Wait for duration
await step.sleep('wait', '1 hour');
// step.sleepUntil - Wait until timestamp
await step.sleepUntil('wait until', Date.now() + 3600000);
// step.waitForEvent - Wait for external event
const event = await step.waitForEvent('payment received', 'payment.completed', {
timeout: '30 minutes'
});
CRITICAL: All I/O (fetch, KV, D1, R2) must happen inside step.do() callbacks!
Reference: See references/workflow-patterns.md for all patterns
Critical Rules
Always Do ✅
✅ Perform all I/O inside step.do() - Required for durability ✅ Use named steps - Makes debugging easier ✅ Return JSON-serializable data from steps - Required for state persistence ✅ Use step.sleep() for delays - Don't use setTimeout() ✅ Handle errors explicitly - Use try/catch in step callbacks ✅ Use NonRetryableError for permanent failures - Stops retries
Workflow Patterns: See references/workflow-patterns.md for:
- Sequential workflows
- Parallel execution
- Event-driven workflows
- Scheduled workflows
- Human-in-the-loop workflows
Never Do ❌
❌ Never do I/O outside step.do() - Will fail with "I/O context" error ❌ Never use setTimeout() or setInterval() - Use step.sleep() instead ❌ Never return non-serializable data - Functions, Promises, etc. will fail ❌ Never hardcode timeouts - Use workflow config ❌ Never ignore NonRetryableError - Indicates permanent failure
Top 5 Critical Errors
Error #1: I/O Context Error ⚠️
Error:
Cannot perform I/O on behalf of a different request
Cause: Performing I/O outside step.do() callback
Solution:
// ❌ WRONG
const data = await fetch('https://api.example.com');
await step.do('use data', async () => {
return data; // Error!
});
// ✅ CORRECT
const data = await step.do('fetch data', async () => {
const response = await fetch('https://api.example.com');
return await response.json();
});
Error #2: Serialization Error
Error:
Cannot serialize workflow state
Cause: Returning non-JSON-serializable data from step
Solution:
// ❌ WRONG
await step.do('process', async () => {
return { fn: () => {} }; // Functions not serializable
});
// ✅ CORRECT
await step.do('process', async () => {
return { result: 'data' }; // JSON-serializable
});
Error #3: NonRetryableError Not Thrown
Error: Workflow retries forever on permanent failures
Solution:
import