Build your first Workflow
Workflows allow you to build durable, multi-step applications using the Workers platform. A Workflow can automatically retry, persist state, run for hours or days, and coordinate between third-party APIs.
You can build Workflows to post-process file uploads to R2 object storage, automate generation of Workers AI embeddings into a Vectorize vector database, or to trigger user lifecycle emails using Email Service.
In this guide, you will create and deploy a Workflow that fetches data, pauses, and processes results.
If you want to skip the steps and pull down the complete Workflow we are building in this guide, run:
npm create cloudflare@latest workflows-starter -- --template "cloudflare/workflows-starter"Use this option if you are familiar with Cloudflare Workers or want to explore the code first and learn the details later.
Follow the steps below to learn how to build a Workflow from scratch.
- Sign up for a Cloudflare account ↗.
- Install
Node.js↗.
Node.js version manager
Use a Node version manager like Volta ↗ or nvm ↗ to avoid permission issues and change Node.js versions. Wrangler, discussed later in this guide, requires a Node version of 16.17.0 or later.
-
Open a terminal and run the
create cloudflare(C3) CLI tool to create your Worker project:Terminal window npm create cloudflare@latest -- my-workflowTerminal window yarn create cloudflare my-workflowTerminal window pnpm create cloudflare@latest my-workflowFor setup, select the following options:
- For What would you like to start with?, choose
Hello World example. - For Which template would you like to use?, choose
Worker only. - For Which language do you want to use?, choose
TypeScript. - For Do you want to use git for version control?, choose
Yes. - For Do you want to deploy your application?, choose
No(we will be making some changes before deploying).
- For What would you like to start with?, choose
-
Move into your new project directory:
Terminal window cd my-workflowWhat files did C3 create?
In your project directory, C3 will have generated the following:
wrangler.jsonc: Your Wrangler configuration file.src/index.ts: A minimal Worker written in TypeScript.package.json: A minimal Node dependencies configuration file.tsconfig.json: TypeScript configuration.
-
Create a new file
src/workflow.ts:src/workflow.ts import { WorkflowEntrypoint, WorkflowStep } from "cloudflare:workers";import type { WorkflowEvent } from "cloudflare:workers";type Params = { name: string };type IPResponse = { result: { ipv4_cidrs: string[] } };export class MyWorkflow extends WorkflowEntrypoint<Env, Params> {async run(event: WorkflowEvent<Params>, step: WorkflowStep) {const data = await step.do("fetch data", async () => {const response = await fetch("https://api.cloudflare.com/client/v4/ips");return await response.json<IPResponse>();});await step.sleep("pause", "20 seconds");const result = await step.do("process data",{ retries: { limit: 3, delay: "5 seconds", backoff: "linear" } },async () => {return {name: event.payload.name,ipCount: data.result.ipv4_cidrs.length,};},);return result;}}A Workflow extends
WorkflowEntrypointand implements arunmethod. This code also passes in ourParamstype as a type parameter so that events that trigger our Workflow are typed.The
stepobject is the core of the Workflows API. It provides methods to define durable steps in your Workflow:step.do(name, callback)- Executes code and persists the result. If the Workflow is interrupted or retried, it resumes from the last successful step rather than re-running completed work.step.sleep(name, duration)- Pauses the Workflow for a duration (e.g.,"10 seconds","1 hour").
You can pass a retry configuration to
step.do()to customize how failures are handled. See the full step API for additional methods likesleepUntilandwaitForEvent.When deciding whether to break code into separate steps, ask yourself: "Do I want all of this code to run again if just one part fails?" Separate steps are ideal for operations like calling external APIs, querying databases, or reading files from storage — if a later step fails, your Workflow can retry from that point using data already fetched, avoiding redundant API calls or database queries.
For more guidance on how to define your Workflow logic, refer to Rules of Workflows.
-
Open
wrangler.jsonc, which is your Wrangler configuration file for your Workers project and your Workflow, and add theworkflowsconfiguration:{"$schema": "node_modules/wrangler/config-schema.json","name": "my-workflow","main": "src/index.ts","compatibility_date": "2025-12-21","observability": {"enabled": true},"workflows": [{"name": "my-workflow","binding": "MY_WORKFLOW","class_name": "MyWorkflow"}]}"$schema" = "node_modules/wrangler/config-schema.json"name = "my-workflow"main = "src/index.ts"compatibility_date = "2025-12-21"[observability]enabled = true[[workflows]]name = "my-workflow"binding = "MY_WORKFLOW"class_name = "MyWorkflow"The
class_namemust match your exported class, andbindingis the variable name you use to access the Workflow in your code (likeenv.MY_WORKFLOW).You can also access bindings (such as KV, R2, or D1) via
this.envwithin your Workflow. For more information on bindings within Workers, refer to Bindings (env). -
Now, generate types for your bindings:
Terminal window npx wrangler typesThis creates a
worker-configuration.d.tsfile with theEnvtype that includes yourMY_WORKFLOWbinding.
Now, you'll need a place to call your Workflow.
-
Replace
src/index.tswith a fetch handler to start and check Workflow instances:src/index.ts export { MyWorkflow } from "./workflow";export default {async fetch(request: Request, env: Env): Promise<Response> {const url = new URL(request.url);const instanceId = url.searchParams.get("instanceId");if (instanceId) {const instance = await env.MY_WORKFLOW.get(instanceId);return Response.json(await instance.status());}const instance = await env.MY_WORKFLOW.create();return Response.json({ instanceId: instance.id });},} satisfies ExportedHandler<Env>;
-
Start a local development server:
Terminal window npx wrangler dev -
To start a Workflow instance, open a new terminal window and run:
Terminal window curl http://localhost:8787An
instanceIdwill be automatically generated:{ "instanceId": "abc-123-def" } -
Check the status using the returned
instanceId:Terminal window curl "http://localhost:8787?instanceId=abc-123-def"The Workflow will progress through its steps. After about 20 seconds (the sleep duration), it will complete.
-
Deploy your Workflow:
Terminal window npx wrangler deployTest in production using the same curl commands against your deployed URL. You can also trigger a workflow instance in production via Workers, Wrangler, or the Cloudflare dashboard.
Once deployed, you can also inspect Workflow instances with the CLI:
Terminal window npx wrangler workflows instances describe my-workflow latestThe output of
instances describeshows:- The status (success, failure, running) of each step
- Any state emitted by the step
- Any
sleepstate, including when the Workflow will wake up - Retries associated with each step
- Errors, including exception messages
Was this helpful?
- Resources
- API
- New to Cloudflare?
- Directory
- Sponsorships
- Open Source
- Support
- Help Center
- System Status
- Compliance
- GDPR
- Company
- cloudflare.com
- Our team
- Careers
- © 2026 Cloudflare, Inc.
- Privacy Policy
- Terms of Use
- Report Security Issues
- Trademark
-