Skip to content
Docs

How to migrate from Cloudflare Workflows to Vercel Workflows

Migrate from Cloudflare Workflows to Vercel Workflows by mapping WorkflowEntrypoint, step.do, and waitForEvent to workflow directives and hooks.

Ben SabicContent Engineer

To migrate from Cloudflare Workflows to Vercel 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.

Copy link to headingOverview

  • 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

Copy link to headingConcept 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' and 'use step', 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/APIVercel Workflows equivalent or migration approachMigration 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()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 packageCall 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 bindingstart(workflow, [args]) from workflow/apiCall it from server-side code such as a route handler.
Bindings and service integrationsEnvironment variables and provider SDKs inside stepsMigrate each binding separately. Keep all side effects in 'use step' functions.
Rollback logicExplicit compensation in the workflowTrack completed work and undo it in reverse order when a later step throws.
Workflow observabilityWorkflows in the Vercel dashboardEvery 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.

Copy link to headingWhat 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.

Copy link to headingMigration procedure

Copy link to heading1. Install the Workflow SDK

Add the workflow package to your project:

Terminal
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 carries the source, the API surface, and migration reference material.

Copy link to heading2. 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:

workflows/order.ts
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.

Copy link to heading3. 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:

workflows/steps.ts
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.

Copy link to heading4. 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:

app/api/orders/route.ts
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.

Copy link to heading5. 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 covers the platform-level pieces. If the surrounding app is a TanStack Start project, follow the TanStack Start migration guide for the framework move. Teams deciding where the app itself should run can compare the platforms in Next.js on Vercel vs Cloudflare.

Copy link to heading6. Deploy and verify

Deploy the project, trigger a test run, then open your Vercel dashboard, select the project, and go to Workflows. Confirm each migrated workflow appears, its steps complete in order, and sleeps and event waits suspend and resume as expected.

Copy link to headingEvent-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 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:

workflows/approval.ts
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:

app/api/approve/route.ts
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 pauses on user input and resumes from Slack events.

Copy link to headingReliability and operational migration

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

workflows/order-saga.ts
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:

workflows/steps.ts
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 for current quotas and billing dimensions instead of copying numbers from your old configuration.

Copy link to headingFAQ

Copy link to headingCan 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.

Copy link to headingHow 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.

Copy link to headingDo 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.

Copy link to headingCan 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.

Copy link to headingWhere 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.

Copy link to headingNext steps

Related documentation

More Vercel Workflows guides