Skip to content
Development
Skill

/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` /

From plugin
workflow
2.4k6 skills1 agent1 command
Install
$ npx -y skills add vercel/workflow --skill migrating-workflow-v4-to-v5 --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/migrating-workflow-v4-to-v5

Context preview

The summary Claude sees to decide when to auto-load this skill.

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` /

SKILL.md

migrating-workflow-v4-to-v5.SKILL.md
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.10'

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
Ships withworkflow

Workflow SDK: Build durable, reliable, and observable apps and AI Agents in TypeScript

Get the whole plugin
Stats
2,395
Stars
360
Forks
Active
Maintenance
TypeScript
Language
Apache-2.0
License
13h ago
Last commit
10mo ago
Created

Repo: vercel/workflow

Other skills on workflow.