/migrating-workflow-v4-to-v5
Upgrades an app from Workflow SDK 4.x to 5.0. Use when bumping the `workflow` / `@workflow/*` dependencies to v5, or when hitting removed v4 APIs — `runStep`, `stepEntrypoint`, `workflow/internal/private`, `@workflow/core/private`, `writeToStream` / `closeStream` /
Claude Code
$ npx -y skills add vercel/workflow --skill migrating-workflow-v4-to-v5 --agent claude-code
Codex
$ npx skills add vercel/workflow --skill migrating-workflow-v4-to-v5 -a codex -y
Cursor
$ npx skills add vercel/workflow --skill migrating-workflow-v4-to-v5 -a cursor -y
Gemini CLI
$ npx skills add vercel/workflow --skill migrating-workflow-v4-to-v5 -a gemini-cli -y
GitHub Copilot
$ npx skills add vercel/workflow --skill migrating-workflow-v4-to-v5 -a github-copilot -y
OpenCode
$ npx skills add vercel/workflow --skill migrating-workflow-v4-to-v5 -a opencode -y
Windsurf
$ npx skills add vercel/workflow --skill migrating-workflow-v4-to-v5 -a windsurf -y
SKILL.md
migrating-workflow-v4-to-v5.SKILL.mdname: migrating-workflow-v4-to-v5
description: >-
Upgrades an app from Workflow SDK 4.x to 5.0. Use when bumping the `workflow` / `@workflow/*` dependencies to v5, or when hitting removed v4 APIs — `runStep`, `stepEntrypoint`, `workflow/internal/private`, `@workflow/core/private`, `writeToStream` / `closeStream` / `readFromStream` on a World, `world.steps.get` without a runId, `hook.getConflict()` returning `{ runId }`, `hook.metadata` read synchronously off a `getHookByToken()` result, `experimental_setAttributes`, `createLocalWorld` / `createVercelWorld`, `NestLocalBuilder` imported from `@workflow/nest`, or an SWC transform invoked with `mode: 'client'`.
metadata:
author: Vercel Inc.
version: '0.2.11'Migrating Workflow SDK 4.x to 5.0
Workflow SDK 5.0 keeps the programming model from 4.x. `"use workflow"` / `"use step"`, `start()`, `getRun()`, hooks, webhooks, streams, `sleep()`, retries, and the event log are unchanged, so most application code compiles as-is.
The breaking changes are concentrated in three places:
1. **Runtime entrypoints** (`workflow/api`, `workflow/runtime`) — two exports removed. 2. **The `World` interface** — only relevant if the app implements a custom World or calls `getWorld()` directly. 3. **Build integrations** — `@workflow/nest` subpaths, and private compiler subpaths that were never public.
Do not rewrite workflow or step bodies. If you find yourself restructuring business logic, you have gone outside this migration.
Intake
Before editing, establish:
1. **Which packages are installed.** Read `package.json` for `workflow` and every `@workflow/*` dependency. 2. **Whether the app touches the runtime.** Grep for `getWorld`, `createWorld`, `getWorldHandlers`, `writeToStream`, `readFromStream`, `closeStream`, `listStreamsByRunId`, `getStreamChunks`, `world.steps`, `listByCorrelationId`, `runStep`, `stepEntrypoint`, `internal/private`, `core/private`. 3. **Whether the app implements a custom World.** Grep for `implements World`, `: World`, `createLocalWorld`, `createVercelWorld`, `startWorkflowWorld`. 4. **Which framework integration is in use.** `@workflow/next`, `@workflow/nest`, `@workflow/nitro`, `@workflow/sveltekit`, `@workflow/vite`, `@workflow/nuxt`, `@workflow/astro`, or the CLI. 5. **Whether `hook.getConflict()` is used.** Grep for `getConflict`. 6. **Whether hook metadata is read outside a workflow.** Grep for `getHookByToken` and `resumeHook`, then for `.metadata` on their results. 7. **Whether the app calls the compiler directly.** Grep for `mode: 'client'`, `transformSync`, `swc-plugin-workflow`. Only custom build integrations do this. 8. **Whether `experimental_setAttributes` is used.** Grep for `experimental_setAttributes`.
Report anything in 2–8 that the app does not use as "not applicable" rather than silently skipping it.
Step 1 — bump the dependencies
Move every `workflow` and `@workflow/*` dependency to `^5.0.0`. They are released together and must not be mixed across majors — a 4.x `@workflow/next` against a 5.x `workflow` will fail at build time.
{
"dependencies": {
"workflow": "^5.0.0",
"@workflow/next": "^5.0.0"
}
}Then reinstall and rebuild so the compiler regenerates the workflow/step bundles and the generated routes under `.well-known/workflow/v1/`. Never hand-edit generated output.
Node requirements are unchanged: `^18 || ^20 || ^22 || ^24`.
Step 2 — apply the mechanical rewrites
Apply each rule only where the pattern actually appears.
`getWorld()` and `createWorld()` are async
// v4
const world = getWorld();
// v5
const world = await getWorld();
This also applies to `getWorldHandlers()`. Awaiting was already correct in 4.x, so this edit is safe to make before the dependency bump. Propagate `async` up the call chain rather than wrapping in `.then()` chains.
`createLocalWorld()` and `createVercelWorld()` removed
First-party World packages now expose a single `createWorld()` factory. The arguments are unchanged — this is a rename only.
// v4
import { createLocalWorld } from '@workflow/world-local';
const world = createLocalWorld({ dataDir });
// v5
import { createWorld } from '@workflow/world-local';
const world = createWorld({ dataDir });The same applies to `createVercelWorld` from `@workflow/world-vercel`.
`runStep` removed from `workflow/api`
Call the step function directly. The compiler routes the call through the step runtime.
// v4
import { runStep } from 'workflow/api';
const result = await runStep(chargeCard, [orderId]);
// v5
const result = await chargeCard(orderId);`stepEntrypoint` removed from `workflow/runtime`
Framework integrations generate step routes themselves — delete hand-written step routes that existed only to call `stepEntrypoint`. For a custom host, serve the handlers from `getWorldHandlers()` instead.
`workflow/internal/private` and `@workflow/core/private` removed
These subpaths were never public API. Remove the imports; if generated build output still references them, it is stale — reinstall and rebuild rather than restoring the imports.
Stream methods moved to `world.streams.*` with `runId` first
| v4 | v5 | | --- | --- | | `world.writeToStream(name, runId, chunk)` | `world.streams.write(runId, name, chunk)` | | `world.writeToStreamMulti(name, runId, chunks)` | `world.streams.writeMulti(runId, name, chunks)` | | `world.closeStream(name, runId)` | `world.streams.close(runId, name)` | | `world.readFromStream(name, startIndex?)` | `world.streams.get(runId, name, startIndex?)` | | `world.getStreamChunks(name, runId, options?)` | `world.streams.getChunks(runId, name, options?)` | | `world.listStreamsByRunId(runId)` | `world.streams.list(runId)` |
The argument order flipped, so a rename alone silently passes a stream name where a run ID is expected. Swap the arguments at every call site. `readFromStream` had no `runId` parameter at all — `streams.get` requires one, so thread t
Read more
name: migrating-workflow-v4-to-v5
description: >-
Upgrades an app from Workflow SDK 4.x to 5.0. Use when bumping the `workflow` / `@workflow/*` dependencies to v5, or when hitting removed v4 APIs — `runStep`, `stepEntrypoint`, `workflow/internal/private`, `@workflow/core/private`, `writeToStream` / `closeStream` / `readFromStream` on a World, `world.steps.get` without a runId, `hook.getConflict()` returning `{ runId }`, `hook.metadata` read synchronously off a `getHookByToken()` result, `experimental_setAttributes`, `createLocalWorld` / `createVercelWorld`, `NestLocalBuilder` imported from `@workflow/nest`, or an SWC transform invoked with `mode: 'client'`.
metadata:
author: Vercel Inc.
version: '0.2.11'Migrating Workflow SDK 4.x to 5.0
Workflow SDK 5.0 keeps the programming model from 4.x. `"use workflow"` / `"use step"`, `start()`, `getRun()`, hooks, webhooks, streams, `sleep()`, retries, and the event log are unchanged, so most application code compiles as-is.
The breaking changes are concentrated in three places:
1. **Runtime entrypoints** (`workflow/api`, `workflow/runtime`) — two exports removed. 2. **The `World` interface** — only relevant if the app implements a custom World or calls `getWorld()` directly. 3. **Build integrations** — `@workflow/nest` subpaths, and private compiler subpaths that were never public.
Do not rewrite workflow or step bodies. If you find yourself restructuring business logic, you have gone outside this migration.
Intake
Before editing, establish:
1. **Which packages are installed.** Read `package.json` for `workflow` and every `@workflow/*` dependency. 2. **Whether the app touches the runtime.** Grep for `getWorld`, `createWorld`, `getWorldHandlers`, `writeToStream`, `readFromStream`, `closeStream`, `listStreamsByRunId`, `getStreamChunks`, `world.steps`, `listByCorrelationId`, `runStep`, `stepEntrypoint`, `internal/private`, `core/private`. 3. **Whether the app implements a custom World.** Grep for `implements World`, `: World`, `createLocalWorld`, `createVercelWorld`, `startWorkflowWorld`. 4. **Which framework integration is in use.** `@workflow/next`, `@workflow/nest`, `@workflow/nitro`, `@workflow/sveltekit`, `@workflow/vite`, `@workflow/nuxt`, `@workflow/astro`, or the CLI. 5. **Whether `hook.getConflict()` is used.** Grep for `getConflict`. 6. **Whether hook metadata is read outside a workflow.** Grep for `getHookByToken` and `resumeHook`, then for `.metadata` on their results. 7. **Whether the app calls the compiler directly.** Grep for `mode: 'client'`, `transformSync`, `swc-plugin-workflow`. Only custom build integrations do this. 8. **Whether `experimental_setAttributes` is used.** Grep for `experimental_setAttributes`.
Report anything in 2–8 that the app does not use as "not applicable" rather than silently skipping it.
Step 1 — bump the dependencies
Move every `workflow` and `@workflow/*` dependency to `^5.0.0`. They are released together and must not be mixed across majors — a 4.x `@workflow/next` against a 5.x `workflow` will fail at build time.
{
"dependencies": {
"workflow": "^5.0.0",
"@workflow/next": "^5.0.0"
}
}Then reinstall and rebuild so the compiler regenerates the workflow/step bundles and the generated routes under `.well-known/workflow/v1/`. Never hand-edit generated output.
Node requirements are unchanged: `^18 || ^20 || ^22 || ^24`.
Step 2 — apply the mechanical rewrites
Apply each rule only where the pattern actually appears.
`getWorld()` and `createWorld()` are async
// v4 const world = getWorld(); // v5 const world = await getWorld();
This also applies to `getWorldHandlers()`. Awaiting was already correct in 4.x, so this edit is safe to make before the dependency bump. Propagate `async` up the call chain rather than wrapping in `.then()` chains.
`createLocalWorld()` and `createVercelWorld()` removed
First-party World packages now expose a single `createWorld()` factory. The arguments are unchanged — this is a rename only.
// v4
import { createLocalWorld } from '@workflow/world-local';
const world = createLocalWorld({ dataDir });
// v5
import { createWorld } from '@workflow/world-local';
const world = createWorld({ dataDir });The same applies to `createVercelWorld` from `@workflow/world-vercel`.
`runStep` removed from `workflow/api`
Call the step function directly. The compiler routes the call through the step runtime.
// v4
import { runStep } from 'workflow/api';
const result = await runStep(chargeCard, [orderId]);
// v5
const result = await chargeCard(orderId);`stepEntrypoint` removed from `workflow/runtime`
Framework integrations generate step routes themselves — delete hand-written step routes that existed only to call `stepEntrypoint`. For a custom host, serve the handlers from `getWorldHandlers()` instead.
`workflow/internal/private` and `@workflow/core/private` removed
These subpaths were never public API. Remove the imports; if generated build output still references them, it is stale — reinstall and rebuild rather than restoring the imports.
Stream methods moved to `world.streams.*` with `runId` first
| v4 | v5 | | --- | --- | | `world.writeToStream(name, runId, chunk)` | `world.streams.write(runId, name, chunk)` | | `world.writeToStreamMulti(name, runId, chunks)` | `world.streams.writeMulti(runId, name, chunks)` | | `world.closeStream(name, runId)` | `world.streams.close(runId, name)` | | `world.readFromStream(name, startIndex?)` | `world.streams.get(runId, name, startIndex?)` | | `world.getStreamChunks(name, runId, options?)` | `world.streams.getChunks(runId, name, options?)` | | `world.listStreamsByRunId(runId)` | `world.streams.list(runId)` |
The argument order flipped, so a rename alone silently passes a stream name where a run ID is expected. Swap the arguments at every call site. `readFromStream` had no `runId` parameter at all — `streams.get` requires one, so thread t
Workflow SDK: Build durable, reliable, and observable apps and AI Agents in TypeScript
Other skills on workflow.
More skills from other plugins.
brainstorming
You MUST use this before any creative work - creating features, building components, adding functionality, modifying behavior, or…
ask-matt
Ask which skill or flow fits your situation. A router over the skills in this repo.
Questions about migrating-workflow-v4-to-v5.
- Which coding agents can use migrating-workflow-v4-to-v5?
- migrating-workflow-v4-to-v5 installs in Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot, OpenCode and Windsurf with the skills CLI, using each agent's flag shown on this page. Flowy's auto-invocation, which fires a skill when your prompt matches, runs in Claude Code only.

