Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 22 additions & 15 deletions skills/cloudflare/references/workflows/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,30 +16,35 @@ Durable multi-step applications with automatic retries, state persistence, and l

**Workflow**: Class extending `WorkflowEntrypoint` with `run` method
**Instance**: Single execution with unique ID & independent state
**Steps**: Independently retriable units via `step.do()` - API calls, DB queries, AI invocations
**Steps**: Independently retryable units via `step.do()` - API calls, DB queries, AI invocations
**State**: Persisted from step returns; step name = cache key

## Quick Start

```typescript
import { WorkflowEntrypoint, WorkflowStep, WorkflowEvent } from 'cloudflare:workers';
import {
WorkflowEntrypoint,
WorkflowStep,
WorkflowEvent,
} from "cloudflare:workers";

type Env = { MY_WORKFLOW: Workflow; DB: D1Database };
type Params = { userId: string };

export class MyWorkflow extends WorkflowEntrypoint<Env, Params> {
async run(event: WorkflowEvent<Params>, step: WorkflowStep) {
const user = await step.do('fetch user', async () => {
return await this.env.DB.prepare('SELECT * FROM users WHERE id = ?')
.bind(event.params.userId).first();
});

await step.sleep('wait 7 days', '7 days');

await step.do('send reminder', async () => {
await sendEmail(user.email, 'Reminder!');
});
}
async run(event: WorkflowEvent<Params>, step: WorkflowStep) {
const user = await step.do("fetch user", async () => {
return await this.env.DB.prepare("SELECT * FROM users WHERE id = ?")
.bind(event.params.userId)
.first();
});

await step.sleep("wait 7 days", "7 days");

await step.do("send reminder", async () => {
await sendEmail(user.email, "Reminder!");
});
}
}
```

Expand All @@ -54,16 +59,18 @@ export class MyWorkflow extends WorkflowEntrypoint<Env, Params> {

## Reading Order

**Getting Started:** configuration.md → api.md → patterns.md
**Getting Started:** configuration.md → api.md → patterns.md
**Troubleshooting:** gotchas.md

## In This Reference

- [configuration.md](./configuration.md) - wrangler.jsonc setup, step config, bindings
- [api.md](./api.md) - Step APIs, instance management, sleep/parameters
- [patterns.md](./patterns.md) - Common workflows, testing, orchestration
- [gotchas.md](./gotchas.md) - Timeouts, limits, debugging strategies

## See Also

- [durable-objects](../durable-objects/) - Alternative stateful approach
- [queues](../queues/) - Message-driven workflows
- [workers](../workers/) - Entry point for workflow instances
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { Render, Tabs, TabItem, Card, YouTube } from "~/components";

Cloudflare Workflows provides durable execution capabilities, allowing developers to create reliable, repeatable workflows that run in the background. Workflows are designed to resume execution even if the underlying compute fails, ensuring that tasks complete eventually. They are built on top of Cloudflare Workers and handle scaling and provisioning automatically.

Workflows are triggered by events, such as Event Notifications consumed from a Queue, HTTP requests, another Worker, or even scheduled timers. Individual steps within a Workflow are designed as retriable units of work. The state is persisted between steps, allowing workflows to resume from the last successful step after failures. Workflows automatically generate metrics for each step, aiding in debugging and observability.
Workflows are triggered by events, such as Event Notifications consumed from a Queue, HTTP requests, another Worker, or even scheduled timers. Individual steps within a Workflow are designed as retryable units of work. The state is persisted between steps, allowing workflows to resume from the last successful step after failures. Workflows automatically generate metrics for each step, aiding in debugging and observability.

<Card>
<YouTube id="slS4RBV0SBk" />
Expand Down Expand Up @@ -73,7 +73,7 @@ Workflows are triggered by events, such as Event Notifications consumed from a Q

#### Workflow Steps

Each discrete, retriable task in the workflow is defined using `await step.do()`.
Each discrete, retryable task in the workflow is defined using `await step.do()`.

##### Content Moderation

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@ sidebar:
order: 1
---

import { WranglerConfig, TypeScriptExample } from "~/components";
import {
WranglerConfig,
TypeScriptExample,
PackageManagers,
} from "~/components";

Best practices for Workers based on production patterns, Cloudflare's own internal usage, and common issues seen across the developer community.

Expand Down Expand Up @@ -56,9 +60,7 @@ Do not hand-write your `Env` interface. Run [`wrangler types`](/workers/wrangler

Re-run `wrangler types` whenever you add or rename a binding.

```bash
wrangler types
```
<PackageManagers type="exec" pkg="wrangler" args="types" />

<TypeScriptExample filename="src/index.ts">

Expand Down Expand Up @@ -104,12 +106,17 @@ Secrets (API keys, tokens, database credentials) must never appear in your Wrang

</WranglerConfig>

```bash
# Store secrets securely
wrangler secret put API_KEY
To add a secret, run the following command and provide the secret interactively when prompted:

<PackageManagers type="exec" pkg="wrangler" args="secret put API_KEY" />

You can also pipe secrets from other tools or environment variables:

# For local development, use .env (make sure it is in .gitignore)
# API_KEY=sk-test-abc123
```bash
# Pipe from another CLI tool
npx some-cli-tool --get-secret | npx wrangler secret put API_KEY
# Pipe from an environment variable or .env file
echo "$API_KEY" | npx wrangler secret put API_KEY
```

For more information, refer to [Secrets](/workers/configuration/secrets/).
Expand All @@ -118,7 +125,7 @@ For more information, refer to [Secrets](/workers/configuration/secrets/).

[Wrangler environments](/workers/wrangler/environments/) let you deploy the same code to separate Workers for production, staging, and development. Each environment creates a distinct Worker named `{name}-{env}` (for example, `my-api-production` and `my-api-staging`).

Treat the root configuration as your base (shared settings), and override per environment. The root Worker (without an environment suffix) is a separate deployment. If you do not intend to use it, do not deploy without specifying an environment.
Each environment is treated separately. Bindings and vars need to be declared per environment and are not inherited. See [non-inheritable keys](/workers/wrangler/configuration/#non-inheritable-keys). The root Worker (without an environment suffix) is a separate deployment. If you do not intend to use it, do not deploy without specifying an environment using `--env`.

<WranglerConfig>

Expand All @@ -129,7 +136,7 @@ Treat the root configuration as your base (shared settings), and override per en
"compatibility_date": "$today",
"compatibility_flags": ["nodejs_compat"],

// Shared bindings go in the root config
// This binding only applies to the root Worker
"kv_namespaces": [{ "binding": "CACHE", "id": "dev-kv-id" }],

"env": {
Expand All @@ -153,11 +160,9 @@ Treat the root configuration as your base (shared settings), and override per en

</WranglerConfig>

```bash
# Deploy to a specific environment
wrangler deploy --env production
wrangler deploy --env staging
```
With this configuration file, to deploy to staging:

<PackageManagers type="exec" pkg="wrangler" args="deploy --env staging" />

For more information, refer to [Environments](/workers/wrangler/environments/).

Expand All @@ -166,7 +171,7 @@ For more information, refer to [Environments](/workers/wrangler/environments/).
Workers support two routing mechanisms, and they serve different purposes:

- **[Custom domains](/workers/configuration/routing/custom-domains/)**: The Worker **is** the origin. Cloudflare creates DNS records and SSL certificates automatically. Use this when your Worker handles all traffic for a hostname.
- **[Routes](/workers/configuration/routing/routes/)**: The Worker runs **in front of** an existing origin server. You must have a proxied (orange-clouded) DNS record for the hostname before adding a route.
- **[Routes](/workers/configuration/routing/routes/)**: The Worker runs **in front of** an existing origin server. You must have a Cloudflare proxied (orange-clouded) DNS record for the hostname before adding a route.

The most common mistake with routes is missing the DNS record. Without a proxied DNS record, requests to the hostname return `ERR_NAME_NOT_RESOLVED` and never reach your Worker. If you do not have a real origin, add a proxied `AAAA` record pointing to `100::` as a placeholder.

Expand Down Expand Up @@ -272,7 +277,7 @@ For more information, refer to [Streams](/workers/runtime-apis/streams/).

[`ctx.waitUntil()`](/workers/runtime-apis/context/) lets you perform work after the response is sent to the client, such as analytics, cache writes, non-critical logging, or webhook notifications. This keeps your response fast while still completing background tasks.

There are two common pitfalls: destructuring `ctx` (which loses the `this` binding and throws "Illegal invocation"), and exceeding the 30-second time limit after the response is sent.
There are two common pitfalls: destructuring `ctx` (which loses the `this` binding and throws "Illegal invocation"), and exceeding the 30-second waitUntil time limit after the response is sent.

<TypeScriptExample filename="src/index.ts">

Expand Down Expand Up @@ -365,7 +370,7 @@ export default {

### Use Queues and Workflows for async and background work

Long-running, retriable, or non-urgent tasks should not block a request. Use [Queues](/queues/) and [Workflows](/workflows/) to move work out of the critical path. They serve different purposes:
Long-running, retryable, or non-urgent tasks should not block a request. Use [Queues](/queues/) and [Workflows](/workflows/) to move work out of the critical path. They serve different purposes:

**Use Queues when** you need to decouple a producer from a consumer. Queues are a message broker: one Worker sends a message, another Worker processes it later. They are the right choice for fan-out (one event triggers many consumers), buffering and batching (aggregate messages before writing to a downstream service), and simple single-step background jobs (send an email, fire a webhook, write a log). Queues provide at-least-once delivery with configurable retries per message.

Expand Down
2 changes: 1 addition & 1 deletion src/content/docs/workflows/build/rules-of-workflows.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import { WranglerConfig, TypeScriptExample } from "~/components";

A Workflow contains one or more steps. Each step is a self-contained, individually retriable component of a Workflow. Steps may emit (optional) state that allows a Workflow to persist and continue from that step, even if a Workflow fails due to a network or infrastructure issue.
A Workflow contains one or more steps. Each step is a self-contained, individually retryable component of a Workflow. Steps may emit (optional) state that allows a Workflow to persist and continue from that step, even if a Workflow fails due to a network or infrastructure issue.

This is a small guidebook on how to build more resilient and correct Workflows.

Expand Down Expand Up @@ -566,7 +566,7 @@
async fetch(req: Request, env: Env): Promise<Response> {
let instances = [
{ id: "user1", params: { name: "John" } },
{ id: "user2", params: { name: "Jane" } },

Check warning on line 569 in src/content/docs/workflows/build/rules-of-workflows.mdx

View workflow job for this annotation

GitHub Actions / Semgrep

semgrep.style-guide-potential-date-month

Potential month found. Documentation should strive to represent universal truth, not something time-bound. (add [skip style guide checks] to commit message to skip)
{ id: "user3", params: { name: "Alice" } },
{ id: "user4", params: { name: "Bob" } },
];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
3. Analyze and compare them
4. Return a recommendation

Each LLM call and tool call becomes a <GlossaryTooltip term="step">step</GlossaryTooltip> — a self-contained, individually retriable unit of work. If any step fails, Workflows retries it automatically. If the entire Workflow crashes mid-task, it resumes from the last successful step.
Each LLM call and tool call becomes a <GlossaryTooltip term="step">step</GlossaryTooltip> — a self-contained, individually retryable unit of work. If any step fails, Workflows retries it automatically. If the entire Workflow crashes mid-task, it resumes from the last successful step.

| Challenge | Solution with Workflows |
| ---------------------------- | --------------------------------------------------------- |
Expand Down Expand Up @@ -261,7 +261,7 @@
{ retries: { limit: 3, delay: "10 seconds", backoff: "exponential" } },
async () => {
const msg = await client.messages.create({
model: "claude-sonnet-4-5-20250929",

Check warning on line 264 in src/content/docs/workflows/get-started/durable-agents.mdx

View workflow job for this annotation

GitHub Actions / Semgrep

semgrep.style-guide-potential-date-year

Potential year found. Documentation should strive to represent universal truth, not something time-bound. (add [skip style guide checks] to commit message to skip)
max_tokens: 4096,
tools: toolDefinitions,
messages,
Expand Down Expand Up @@ -342,7 +342,7 @@
This is especially important for:

- **LLM calls**: Expensive and slow, should not repeat unnecessarily
- **External APIs**: May have rate limits or side effects

Check warning on line 345 in src/content/docs/workflows/get-started/durable-agents.mdx

View workflow job for this annotation

GitHub Actions / Semgrep

semgrep.style-guide-potential-date-month

Potential month found. Documentation should strive to represent universal truth, not something time-bound. (add [skip style guide checks] to commit message to skip)
- **Idempotency**: Some tools (like sending emails) should not run twice

</Details>
Expand Down
2 changes: 1 addition & 1 deletion src/content/glossary/workflows.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ entries:

- term: "step"
general_definition: |-
A step is self-contained, individually retriable component of a Workflow. Steps may emit (optional) state that allows a Workflow to persist and continue from that step, even if a Workflow fails due to a network or infrastructure issue. A Workflow can have one or more steps up to the [step limit](/workflows/reference/limits/).
A step is self-contained, individually retryable component of a Workflow. Steps may emit (optional) state that allows a Workflow to persist and continue from that step, even if a Workflow fails due to a network or infrastructure issue. A Workflow can have one or more steps up to the [step limit](/workflows/reference/limits/).

- term: "Event"
general_definition: |-
Expand Down
Loading