---
title: How to migrate from Cloudflare Workflows to Vercel Workflows
description: Migrate from Cloudflare Workflows to Vercel Workflows by mapping WorkflowEntrypoint, step.do, and waitForEvent to workflow directives and hooks.
url: /kb/guide/migrate-cloudflare-workflows-to-vercel-workflows
canonical_url: "https://vercel.com/kb/guide/migrate-cloudflare-workflows-to-vercel-workflows"
published: 2026-08-19
last_updated: 2026-08-19
authors: Ben Sabic
related:
  - /docs/workflows
  - /docs/workflows/concepts
  - /kb/guide/migrate-to-vercel-from-cloudflare
  - /kb/guide/migrate-a-tanstack-start-app-from-cloudflare-to-vercel
  - /kb/guide/next-js-on-vercel-vs-cloudflare
  - /kb/guide/stateful-slack-bots-with-vercel-workflow
  - /docs/workflows/pricing
  - /docs/queues
install_vercel_plugin: npx plugins add vercel/vercel-plugin
---

To migrate from Cloudflare Workflows to [Vercel Workflows](https://vercel.com/docs/workflows), rewrite your `WorkflowEntrypoint` class as a function marked `'use workflow'`, move each `step.do()` callback into its own `'use step'` function, replace `step.sleep()` with `sleep()`, translate event waits into hooks or webhooks, and start runs with `start()` from server-side code.

Both platforms run durable, resumable functions, but their APIs differ in shape. Treat the migration as a semantic translation, not a one-to-one port: some constructs map directly, others need a different pattern, and a few need a redesign.

## Overview

- Map each Cloudflare Workflows construct to its Vercel Workflows equivalent
  
- Rewrite entrypoint classes, steps, and triggers in the Workflow SDK's format
  
- Translate `step.waitForEvent()` and `sendEvent()` interactions to one hook or webhook resume surface
  
- Recreate rollback behavior with explicit compensation
  
- Verify migrated runs in the Vercel dashboard
  

## Concept and API mapping

How do Cloudflare Workflows concepts map to Vercel Workflows?

Vercel Workflows builds on the Workflow SDK, which uses two directives, [`'use workflow'`](https://vercel.com/docs/workflows/concepts) [and](https://vercel.com/docs/workflows/concepts) [`'use step'`](https://vercel.com/docs/workflows/concepts), to turn ordinary async functions into durable workflows. There is no base class to extend and no step context object to thread through your code.

| Cloudflare Workflows concept/API                   | Vercel Workflows equivalent or migration approach                                                      | Migration notes                                                                    |
| -------------------------------------------------- | ------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------- |
| `WorkflowEntrypoint` class with `run(event, step)` | An async function marked `'use workflow'`                                                              | The function body replaces `run()`. Function parameters replace the event payload. |
| [`step.do`](http://step.do)`()`                    | A separate async function marked `'use step'`                                                          | Each step compiles into an isolated route and retries automatically on failure.    |
| `step.sleep()`                                     | `sleep()` from the `workflow` package                                                                  | Call it inside the workflow function. The run consumes no compute while paused.    |
| `step.waitForEvent()` and `sendEvent()`            | Exactly one resume surface: `createHook()` with `resumeHook()`, or `createWebhook()`                   | Pick one surface per event interaction. See the event migration section below.     |
| Starting a workflow through a binding              | `start(workflow, [args])` from `workflow/api`                                                          | Call it from server-side code such as a route handler.                             | | Bindings and service integrations                  | Environment variables and provider SDKs inside steps                                                   | Migrate each binding separately. Keep all side effects in `'use step'` functions.  | | Rollback logic                                     | Explicit compensation in the workflow                                                                  | Track completed work and undo it in reverse order when a later step throws.        | | Workflow observability                             | Workflows in the [Vercel dashboard](https://vercel.com/d?to=%2F%5Bteam%5D%2F%5Bproject%5D%2Fworkflows) | Every step, input, output, sleep, and error is recorded automatically.             |

Work through the table row by row for each workflow before touching code. Direct rows like sleeps and steps translate mechanically. The rows for event waits, bindings, and rollback logic drive most of the migration effort, and the sections below cover each of them in sequence.

### What doesn't map

This guide does not map `step.sleepUntil()`, per-step retry and backoff configuration, caller-supplied instance IDs, `createBatch()`, or instance lifecycle controls. It also does not cover automatic rollback handlers (compensation on Vercel Workflows is code you write) or moving in-flight instances between platforms. For these, check the current Workflow SDK reference before planning, redesign around the primitives in the table above, and let running Cloudflare instances finish before cutover.

## Migration procedure

### 1\. Install the Workflow SDK

Add the `workflow` package to your project:

```bash
pnpm i workflow
```

pnpm reports the packages it added and lists `workflow` under `dependencies` with the version it resolved.

The SDK is open source. The [vercel/workflow repository](https://github.com/vercel/workflow) carries the source, the API surface, and migration reference material.

### 2\. Rewrite the entrypoint as a workflow function

Replace the `WorkflowEntrypoint` class with an exported async function marked `'use workflow'`. Parameters you read from the event payload become function arguments:

```typescript
import { sleep } from 'workflow';
import { chargePayment, reserveInventory } from './steps';

export async function orderWorkflow(orderId: string) {
  'use workflow';

  const reservation = await reserveInventory(orderId);
  await sleep('1 day');
  const receipt = await chargePayment(orderId, reservation.id);

  return { orderId, receiptId: receipt.id };
}
```

`step.sleep()` calls become `sleep()` calls directly in the workflow body. The imported `reserveInventory` and `chargePayment` functions are the step functions you write next.

### 3\. Move side effects into step functions

Each `step.do()` callback becomes its own function marked `'use step'`. API calls, database writes, and other side effects belong here, not in the workflow body:

```typescript
export async function reserveInventory(orderId: string) {
  'use step';

  const response = await fetch(`${process.env.INVENTORY_API_URL}/reserve`, {
    method: 'POST',
    body: JSON.stringify({ orderId }),
  });
  return (await response.json()) as { id: string };
}

export async function chargePayment(orderId: string, reservationId: string) {
  'use step';

  const response = await fetch(`${process.env.PAYMENTS_API_URL}/charge`, {
    method: 'POST',
    body: JSON.stringify({ orderId, reservationId }),
  });
  return (await response.json()) as { id: string };
}
```

Steps retry automatically on transient failures, so throw on errors rather than swallowing them.

### 4\. Start runs from server-side code

Cloudflare starts instances through a workflow binding on the environment. On Vercel, import `start` from `workflow/api` and call it from a route handler, server action, or other server-side code:

```typescript
import { start } from 'workflow/api';
import { orderWorkflow } from '@/workflows/order';

export async function POST(request: Request) {
  const { orderId } = await request.json();
  const run = await start(orderWorkflow, [orderId]);
  return Response.json({ runId: run.runId });
}
```

The response confirms that the run started and provides an ID for later lookup.

### 5\. Replace bindings and platform integrations

Workflow code that read Cloudflare bindings needs those resources replaced individually: storage, queues, and secrets each move on their own track.

The [guide to migrating from Cloudflare to Vercel](https://vercel.com/kb/guide/migrate-to-vercel-from-cloudflare) covers the platform-level pieces. If the surrounding app is a TanStack Start project, follow the [TanStack Start migration guide](https://vercel.com/kb/guide/migrate-a-tanstack-start-app-from-cloudflare-to-vercel) for the framework move. Teams deciding where the app itself should run can compare the platforms in [Next.js on Vercel vs Cloudflare](https://vercel.com/kb/guide/next-js-on-vercel-vs-cloudflare).

### 6\. Deploy and verify

Deploy the project, trigger a test run, then open your Vercel dashboard, select the project, and go to [Workflows](https://vercel.com/d?to=%2F%5Bteam%5D%2F%5Bproject%5D%2Fworkflows). Confirm each migrated workflow appears, its steps complete in order, and sleeps and event waits suspend and resume as expected.

## Event-driven workflow migration

Cloudflare workflows pause with `step.waitForEvent()` and resume when something calls `sendEvent()` on the instance.

Vercel Workflows resumes paused runs through hooks and webhooks, and each translated event interaction should use exactly one resume surface, a rule the SDK's own [shared migration patterns](https://github.com/vercel/workflow/blob/main/skills/migrating-to-workflow-sdk/references/shared-patterns.md) spells out:

- Use `createHook()` in the workflow and `resumeHook()` from your server code when your own app resumes the run and you can address it with a deterministic token such as `order:${orderId}:approval`.
  
- Use `createWebhook()` when an external system needs a generated callback URL to resume the run.
  

The `waitForEvent()` call that your own backend resolved translates to a hook:

```typescript
import { createHook } from 'workflow';

export async function approvalWorkflow(orderId: string) {
  'use workflow';

  using approval = createHook<{ approved: boolean }>({
    token: `order:${orderId}:approval`,
  });
  return await approval;
}
```

The route handler that replaces the `sendEvent()` call resumes the run by token:

```typescript
import { resumeHook } from 'workflow/api';

export async function POST(request: Request) {
  const { orderId } = await request.json();
  await resumeHook(`order:${orderId}:approval`, { approved: true });
  return Response.json({ resumed: true });
}
```

Don't pair `createWebhook()` with `resumeHook()`, and don't build a custom callback route when the generated `webhook.url` is the intended resume surface. For a complete event-driven example, the guide to [building stateful Slack bots with Vercel Workflows](https://vercel.com/kb/guide/stateful-slack-bots-with-vercel-workflow) pauses on user input and resumes from Slack events.

## Reliability and operational migration

Cloudflare rollback logic becomes explicit compensation on Vercel Workflows. Track undo actions as steps complete, and run them in reverse order:

```typescript
import {
  chargePayment,
  refundPayment,
  releaseInventory,
  reserveInventory,
} from './steps';

export async function orderSaga(orderId: string) {
  'use workflow';

  const rollbacks: Array<() => Promise<void>> = [];
  try {
    const reservation = await reserveInventory(orderId);
    rollbacks.push(() => releaseInventory(reservation.id));
    const charge = await chargePayment(orderId, reservation.id);
    rollbacks.push(() => refundPayment(charge.id));

    return { orderId, status: 'completed' as const };
  } catch (error) {
    while (rollbacks.length > 0) {
      await rollbacks.pop()!();
    }
    throw error;
  }
}
```

The compensation functions live in the same steps file as the work they undo. Because they are `'use step'` functions, they get the same durability:

```typescript
export async function releaseInventory(reservationId: string) {
  'use step';

  await fetch(`${process.env.INVENTORY_API_URL}/release`, {
    method: 'POST',
    body: JSON.stringify({ reservationId }),
  });
}

export async function refundPayment(chargeId: string) {
  'use step';

  await fetch(`${process.env.PAYMENTS_API_URL}/refund`, {
    method: 'POST',
    body: JSON.stringify({ chargeId }),
  });
}
```

Two operational notes for cutover. First, external writes should be idempotent: pass `getStepMetadata().stepId` as an idempotency key so a retried step doesn't double-charge or double-send. Second, capacity assumptions from Cloudflare don't carry over. Review [Workflows pricing and limits](https://vercel.com/docs/workflows/pricing) for current quotas and billing dimensions instead of copying numbers from your old configuration.

## FAQ

### Can I run my Cloudflare Workflows code on Vercel Workflows without changes?

No. Cloudflare Workflows uses a `WorkflowEntrypoint` class and a step context object, while Vercel Workflows uses `'use workflow'` and `'use step'` directives on plain async functions. The durable-execution concepts carry over, but every entrypoint, step, trigger, and event wait needs rewriting.

### How do I replace step.waitForEvent() and sendEvent() in Vercel Workflows?

Choose exactly one resume surface per event interaction. Use `createHook()` in the workflow with `resumeHook()` in your server code when your own app resumes the run with a deterministic token. Use `createWebhook()` when an external system needs a generated callback URL.

### Do Vercel Workflows steps retry automatically?

Yes. Functions marked `'use step'` get built-in retries and survive failures like network errors or process crashes. Cloudflare's per-step retry and backoff configuration does not translate one-to-one, so review retry behavior in the Workflow SDK reference before relying on specific retry counts or delays.

### Can I migrate in-flight Cloudflare Workflows instances to Vercel Workflows?

This guide does not provide a method to migrate in-flight Cloudflare Workflows instances. Plan a cutover that lets existing Cloudflare instances drain before directing new starts to Vercel Workflows.

### Where do I monitor migrated workflows on Vercel?

In the Vercel dashboard, select your project and go to **Observability**, then **Workflows**. Every step, input, output, sleep, and error is recorded automatically, so you can trace a run end to end without adding instrumentation.

## Next steps

- [Vercel Workflows pricing and limits](https://vercel.com/docs/workflows/pricing)
  
- [Workflow SDK getting started guide](https://workflow-sdk.dev/docs/getting-started)
  
- [Vercel Queues](https://vercel.com/docs/queues)