# Workflows

> 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.

- Skill: `zllovesuki/workflows` (Agent Skill)
- Install (CLI): `npx skillmds@latest add zllovesuki/workflows`
- Raw SKILL.md: https://api.skillmd.com/api/skills/zllovesuki/workflows/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: zllovesuki (https://skillmd.com/u/zllovesuki)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/zllovesuki/workflows

---

# 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

```ts
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

```jsonc
{
  "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.

