| id | overview |
|---|---|
| title | Overview |
TanStack Workflow is a durable execution engine for TypeScript. Workflows are async functions that pause, persist, and resume across process restarts.
The goal is durable workflows without making a workflow platform the center of your application. Your workflow code stays in your app, your durability boundary stays in a store you choose, and the same workflow model can run across Cloudflare, Railway, Netlify, Node, AWS, Vercel, or your own infrastructure.
Most workflow systems solve durability by asking you to adopt their control plane, hosted runtime, or state-machine service. TanStack Workflow is the headless option: a TypeScript engine, explicit durable primitives, a storage contract, and adapters for the environments you already deploy to.
Use TanStack Workflow when you want:
- long-running app logic without long-running processes
- durable steps, sleeps, signals, and approvals in plain TypeScript
- persistence you own or configure
- workflow code that can move between deployment providers
- TanStack-style primitives instead of a separate workflow platform
- A workflow is a closure —
async (ctx) => .... Plain JS control flow. - Every durable call goes through
ctx.*and writes to an append-only event log. - State is derived — reconstructed by replaying the log + re-running the handler. Never persisted directly.
- Pause = handler throws an internal sentinel. Resume = run the handler again; replay short-circuits past completed work.
Input ──┐ ┌── Output (handler's return value)
│ │
▼ │
createWorkflow({...}) │
⇒ handler(ctx) ─────────────┘
│
├─ writes ──▶ Event log (durability + UI transport)
│
└─ reads ◀── RunState (status, version, pause info)
The event log is the source of truth. The browser subscribes to the same log via the runStore.
- Side effects go inside
ctx.step(id, fn). Barefetch()/db.x()outside a step is a determinism violation. - Use
ctx.now()/ctx.uuid()— notDate.now()/crypto.randomUUID(). - Step IDs must be unique per call site. Loops use interpolation:
ctx.step(\charge-${i}`, fn)`. - Helpers take
ctx: WorkflowCtx<TExt>and call primitives through it. No ambient state.
| In the log (durable) | Emit-only (observability) |
|---|---|
STEP_FINISHED / STEP_FAILED |
RUN_STARTED |
SIGNAL_AWAITED / SIGNAL_RESOLVED |
STEP_STARTED |
APPROVAL_REQUESTED / APPROVAL_RESOLVED |
STATE_DELTA |
NOW_RECORDED / UUID_RECORDED |
CUSTOM (ctx.emit) |
RUN_FINISHED / RUN_ERRORED |
Replay reads the durable events. Live subscribers see both.
- Below: any HTTP server (TanStack Start, Hono, Express), any persistence (in-memory, Postgres, Durable Objects).
- Above: agent frameworks (
@tanstack/ai-orchestration), domain workflows in app code. - Beside: TanStack DB (reactive state from the log), TanStack Query (client cache).
The production path has three layers:
@tanstack/workflow-core: the replay engine and workflow authoring API.@tanstack/workflow-runtime: registered workflows, execution store contract, schedules, timers, signals, approvals, leases, and bounded sweeps.- Store and host adapters: Postgres, Cloudflare, Railway, Netlify, Node, Vercel, queues, and other environment-specific capabilities.
This keeps the engine headless without leaving deployment mechanics as an exercise for every user.
@tanstack/workflow-core ships the engine and the in-memory RunStore.
@tanstack/workflow-runtime is the experimental durable runtime layer.
@tanstack/workflow-store-drizzle-postgres, @tanstack/workflow-vercel, and
@tanstack/workflow-netlify are experimental capability adapters.
Start with the Guide for the full production model.