---
title: "09 — Workflows: Durable Multi-Step Orchestration"
uid: workflows
tags: ["long-running", "cloudflare", "durable-execution", "roadmap:cloudflare", "workflows", "retry", "orchestration"]
excerpt: "A Workflow is TypeScript code defining a durable multi-step process — each step checkpointed, retried, resumable. Not a drag-and-drop canvas."
date: 2026-08-13T03:28:17+0000
source: https://www.aveshina.my.id/en/blog/workflows
---

"A visual low-code builder, I think" was my Workflows guess, and it was wrong in the direction that mattered. The correction: **a Workflow is not a drag-and-drop canvas — it's TypeScript code that defines a durable, multi-step process whose execution survives failures, with each step checkpointed so it can be retried or resumed without starting over.** [1] The durability is the feature; the orchestration is the shape.

The framing that landed is the problem this category solves. A non-trivial backend process — "ingest this file, transform it, call three external APIs in order, write the result, send a notification" — is fragile when written as a single function. If step four fails after steps one through three succeeded, the function either retries from scratch (wasteful, possibly side-effecting) or gives up (work lost). A Workflow makes each step a checkpoint: succeed at step three, fail at step four, and the next run picks up at step four with the outputs of one through three intact [1][2].

```figure
<svg viewBox="0 0 740 240" xmlns="http://www.w3.org/2000/svg" class="my-6 w-full max-w-2xl" role="img" aria-label="A Workflow as checkpointed steps. Five step boxes left to right: fetch, transform, call API, write result, notify. Beneath each is a checkpoint marker. Step 4 ('write result') failed and is shown resuming from its checkpoint rather than restarting the whole flow.">
  <defs>
    <marker id="wfarrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto">
      <path d="M0,0 L10,5 L0,10 z" fill="#64748b"/>
    </marker>
  </defs>
  <g font-family="ui-sans-serif, system-ui, sans-serif" text-rendering="geometricPrecision">

    <!-- step boxes -->
    <rect x="20" y="70" width="110" height="50" rx="8" fill="#dcfce7" stroke="#16a34a" stroke-width="1.5"/>
    <text x="75" y="92" font-size="11" font-weight="700" fill="#052e16" text-anchor="middle">1 · fetch</text>
    <text x="75" y="108" font-size="9" fill="#052e16" text-anchor="middle">✓ checkpoint</text>

    <rect x="160" y="70" width="110" height="50" rx="8" fill="#dcfce7" stroke="#16a34a" stroke-width="1.5"/>
    <text x="215" y="92" font-size="11" font-weight="700" fill="#052e16" text-anchor="middle">2 · transform</text>
    <text x="215" y="108" font-size="9" fill="#052e16" text-anchor="middle">✓ checkpoint</text>

    <rect x="300" y="70" width="110" height="50" rx="8" fill="#dcfce7" stroke="#16a34a" stroke-width="1.5"/>
    <text x="355" y="92" font-size="11" font-weight="700" fill="#052e16" text-anchor="middle">3 · call API</text>
    <text x="355" y="108" font-size="9" fill="#052e16" text-anchor="middle">✓ checkpoint</text>

    <rect x="440" y="70" width="110" height="50" rx="8" fill="#fee2e2" stroke="#dc2626" stroke-width="2"/>
    <text x="495" y="92" font-size="11" font-weight="700" fill="#7f1d1d" text-anchor="middle">4 · write</text>
    <text x="495" y="108" font-size="9" fill="#7f1d1d" text-anchor="middle">⚠ failed → retry</text>

    <rect x="580" y="70" width="110" height="50" rx="8" fill="#fef9c3" stroke="#ca8a04" stroke-width="1.5" stroke-dasharray="4,3"/>
    <text x="635" y="92" font-size="11" font-weight="700" fill="#422006" text-anchor="middle">5 · notify</text>
    <text x="635" y="108" font-size="9" fill="#422006" text-anchor="middle">pending</text>

    <!-- arrows -->
    <path d="M130,95 L158,95" fill="none" stroke="#64748b" stroke-width="1.5" marker-end="url(#wfarrow)"/>
    <path d="M270,95 L298,95" fill="none" stroke="#64748b" stroke-width="1.5" marker-end="url(#wfarrow)"/>
    <path d="M410,95 L438,95" fill="none" stroke="#64748b" stroke-width="1.5" marker-end="url(#wfarrow)"/>
    <path d="M550,95 L578,95" fill="none" stroke="#64748b" stroke-width="1.5" stroke-dasharray="4,3" marker-end="url(#wfarrow)"/>

    <!-- checkpoint line -->
    <line x1="75" y1="140" x2="635" y2="140" stroke="#16a34a" stroke-width="1" stroke-dasharray="3,3"/>
    <text x="355" y="160" font-size="10" font-style="italic" fill="#052e16" text-anchor="middle">each step's output is durable — a retry resumes from the last checkpoint, not the start</text>

    <text x="355" y="195" font-size="11" font-weight="700" fill="#1e1b4b" text-anchor="middle">a Workflow is code, not a canvas</text>
    <text x="355" y="215" font-size="10" fill="#64748b" text-anchor="middle">defined in TypeScript, triggered by HTTP or schedule, run by the platform</text>
  </g>
</svg>
```

## Workflows is code, not a canvas

The first correction to my way of thinking: Workflows are authored in TypeScript, the same way a Worker is [1]. I define a class that extends WorkflowEntrypoint, and each step is a method call. There's no drag-and-drop UI in the authoring loop — the "visual" part, if any, is the diagram the platform renders from my code, not the other way around. The reference's "visual, no-code/low-code interface" framing is misleading; the actual authoring experience is typed code with a debugger.

That matters because it changes who Workflows is for. It's not a tool for non-developers to wire up integrations — it's a tool for developers to express long-running, fault-tolerant processes in code they already know how to write, with the platform handling the durability.

## Durable execution: steps as checkpoints

The core abstraction is the **step**. A Workflow is a sequence of steps, and each step is checkpointed — its inputs and outputs are recorded by the platform so that:

- **Retries are scoped to the failing step.** If step four fails, only step four reruns. Steps one through three don't re-execute, which means their side effects don't either.
- **Failures are resumable.** A Workflow that fails can be resumed from the last successful checkpoint rather than restarted.
- **Long-running is first-class.** A Workflow can wait — sleep until a specific time, pause for hours or days — and the platform maintains the state across the wait without holding resources.

This is the category known as "durable execution." The same idea shows up in other ecosystems (Temporal, Airflow, Step Functions); Workflows is Cloudflare's edge-native version of it, with the runtime colocated with the rest of the Workers platform.

## Triggers: HTTP, schedule, event

A Workflow instance is created by a trigger [1][2]. The common entry points:

- **HTTP request.** A Worker receives a request and creates a Workflow instance to handle it — useful when the work is too long or too multi-step for a single request/response.
- **Scheduled (cron).** A cron trigger fires periodically and kicks off a Workflow — useful for periodic batch jobs, rollups, data syncs.
- **Event.** A queue message or a webhook can trigger a Workflow — useful for "when this happens, run this multi-step process."

The decoupling between trigger and execution is the point. The trigger creates the instance quickly; the execution runs as long as it needs to, surviving failures and waits.

## How Workflows fits alongside Queues and Durable Objects

This was the part I had to think through, because three products all touch "async, stateful, fault-tolerant":

- **Queues** — fire-and-forget message delivery with retry and DLQ. Best when the unit of work is a single message and the concern is decoupling producer from consumer.
- **Durable Objects** — single-actor coordination of live concurrent clients. Best when the concern is consistent coordination across clients that are connected _right now_.
- **Workflows** — durable multi-step orchestration across time. Best when the concern is "this process has many ordered steps, any of which can fail, and I need to survive that without re-running the whole thing."

The decision rule: one message, decouple it → Queues. Many clients, coordinate them now → Durable Objects. Many steps, survive failures across time → Workflows.

## How I use this

I reach for Workflows when I have a process with three or more ordered steps where any step can fail and re-running the whole thing would be expensive or side-effecting. The pattern: each external call or meaningful transformation is its own step, so a retry resumes from exactly the boundary that makes sense. For shorter or simpler decoupling, Queues is lighter; for live coordination, Durable Objects is sharper. Workflows earns its complexity when "durable across many steps" is the actual requirement, and not before.

## References

[1] Cloudflare, "Cloudflare Workflows," Cloudflare Docs, 2024. [Online]. Available: [https://developers.cloudflare.com/workflows/](https://developers.cloudflare.com/workflows/)

[2] Cloudflare, "Cloudflare Workflows starter," GitHub, 2024. [Online]. Available: [https://github.com/cloudflare/workflows-starter](https://github.com/cloudflare/workflows-starter)

```quiz
Q: How is a Cloudflare Workflow actually authored?
- As a drag-and-drop canvas in a visual builder
- As TypeScript code extending WorkflowEntrypoint, with each step a method call
- As a YAML manifest listing step names
correct: 1
explain: Despite the "visual workflow" framing, Workflows is authored in code — the same TypeScript you'd write for a Worker. Any diagram is rendered from the code, not the reverse.

Q: A 5-step Workflow fails at step 4. On retry, what re-runs?
- All five steps, from scratch
- Only step 4 — steps 1-3 are checkpointed and not re-executed
- Steps 4 and 5, because they're adjacent
correct: 1
explain: Each step's output is durable. A retry resumes from the last successful checkpoint, so only the failing step re-runs. Earlier steps' side effects are not repeated.

Q: Which problem is Workflows the RIGHT tool for, versus Queues or Durable Objects?
- Decoupling a fast producer from a slow consumer for single messages
- Coordinating live concurrent clients connected via WebSocket right now
- A long-running ordered process where any of several steps can fail and must be resumable
correct: 2
explain: Queues is for single-message decoupling, Durable Objects for live coordination, Workflows for durable multi-step orchestration across time. The resumable-multi-step requirement is Workflows' sweet spot.

Q: How can a Workflow "wait" without holding resources?
- It busy-loops in memory until the wait is over
- The platform checkpoints state and resumes it at the scheduled time — no resources held during the wait
- It can't wait — Workflows must complete within the CPU-time limit
correct: 1
explain: A Workflow can sleep for hours or days. The platform records the state and wakes the instance at the right time, so nothing is held during the wait.

Q: What triggers a Workflow instance?
- Only manual invocation from the dashboard
- HTTP requests, scheduled (cron) triggers, or events like queue messages and webhooks
- Only other Workflows can create Workflow instances
correct: 1
explain: Workflows instances are created by a trigger — HTTP, cron, or event. The trigger creates the instance quickly; execution then runs as long as needed, surviving failures.
```
