workflow-js

A TypeScript SDK for writing durable, reliable serverless functions with automatic retries, delays, and event waiting, backed by Upstash QStash.

SDK
npm
v1.3.3
151stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
71/100Good
Development Activity72
Maintenance76
Community64
Maturity44
Momentum28

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
81/100Excellent
Architecture85
Code Quality85
Innovation78
Learning Curve75

Upstash Workflow (@upstash/workflow) lets developers write long-running, durable serverless functions without managing any queueing or orchestration infrastructure themselves. A workflow is defined as a normal async function that calls context.run, context.sleep, context.call, and similar step primitives; the SDK transparently persists each step’s result via Upstash QStash and replays completed steps on retriggered invocations, so a workflow can span minutes, hours, or days across many serverless invocations while behaving like ordinary sequential code.

Under the hood, every step is serialized into a Step object and appended to an execution log carried in the request/response cycle with QStash. On each HTTP invocation, previously completed steps are parsed back out of that log and “replayed” without re-executing side effects, while the next pending step is dispatched as a new QStash message with the appropriate delay, webhook callback, or third-party call. This request-driven replay model is what allows the SDK to run on stateless serverless platforms (Vercel, Cloudflare Workers, AWS Lambda via various adapters) with no persistent process or external state store beyond QStash itself.

The package ships first-class serve adapters for a wide set of frameworks and platforms — Next.js, Express, Hono, h3, SvelteKit, SolidStart, Astro, Cloudflare Workers, TanStack Start, and React Router — plus a standalone Client for triggering, cancelling, and notifying workflow runs from outside the workflow itself, and a waitForEvent/notify pair for building human-in-the-loop or webhook-driven pauses. Signature verification, dev-server auto-provisioning for local development, and typed step results are handled by the SDK so application code stays free of queueing and retry boilerplate.

What You Get

  • A WorkflowContext with run, sleep, sleepUntil, call, waitForEvent, notify, invoke, createWebhook, and waitForWebhook step primitives for building durable, multi-step logic
  • Ready-made serve adapters for Next.js, Express, Hono, h3, SvelteKit, SolidStart, Astro, Cloudflare Workers, TanStack Start, and React Router
  • A standalone Client for triggering, cancelling, and notifying workflow runs and inspecting dead-letter queue entries from outside the workflow process
  • Automatic request signature verification and a local dev server that auto-provisions QStash credentials so workflows run without a real Upstash account during development
  • Built-in retry, flow-control, and timeout configuration per step, plus lifecycle and debug middleware hooks for observability
  • TypeScript types for step results and payloads, including a typed invoke API for calling one workflow from another and awaiting its result

Common Use Cases

  • Long-running background jobs (report generation, batch processing, media transcoding) that need to survive well past a typical serverless function’s execution-time limit
  • Multi-step business processes such as onboarding flows or order fulfillment that must reliably retry individual steps without re-running the whole process
  • Human-in-the-loop or event-driven flows that pause execution with waitForEvent/waitForWebhook until a payment, approval, or webhook callback arrives
  • Calling third-party APIs from serverless functions without consuming billed runtime while waiting for the response, via context.call
  • Chaining or fanning out to other workflows with context.invoke, useful for composing smaller workflow “functions” into larger pipelines

Under The Hood

Architecture The SDK is organized around a WorkflowContext (src/context/context.ts) that exposes the step primitives (run, sleep, call, waitForEvent, notify, invoke) and delegates their execution to an AutoExecutor (src/context/auto-executor.ts), which decides whether a step’s side effect should run now or whether it has already completed and should be replayed from the parsed execution log. Each step type is a Lazy*Step class (src/context/steps.ts) that knows how to serialize itself into the QStash-carried log format via workflow-parser.ts and workflow-requests.ts. Framework integration lives in per-platform adapter files under platforms/ (nextjs.ts, express.ts, hono.ts, astro.ts, cloudflare.ts, etc.), all of which call the shared serveBase handler in src/serve/index.ts, which parses the incoming QStash request, verifies its signature, reconstructs the WorkflowContext, and either triggers the first invocation or continues execution up to the next step. A separate Client (src/client/index.ts) provides an out-of-band API for triggering, cancelling, and notifying runs, and for querying the dead-letter queue. This request-per-step, replay-on-invocation design is what lets a workflow live entirely inside QStash’s message queue rather than in an always-on process. Tech Stack Written entirely in TypeScript (99%+ of the codebase), built with tsup for dual ESM/CJS output across many subpath exports (/nextjs, /express, /hono, /astro, /cloudflare, /svelte, /solidjs, /tanstack, /react-router, /h3), and published from a single @upstash/workflow package. The only runtime dependency is @upstash/qstash, which supplies the underlying message queue client, request signature verification (Receiver), and the local dev server; zod is a peer dependency used for payload/event typing in some call sites. Bun is used as the primary development and test runner (bun test), with commitlint, husky, and prettier enforcing commit and formatting conventions, and a GitHub Actions CI workflow that runs tests on pull requests plus a scheduled nightly run and a release pipeline gated on GitHub releases. Code Quality Testing is extensive and colocated with source (*.test.ts next to each implementation file), covering the executor, context, workflow parser, workflow requests, client, DLQ, authorization, and multiple platform-specific serve paths, run via Bun’s built-in test runner. Error handling is explicit and typed: dedicated error classes (WorkflowNonRetryableError, WorkflowRetryAfterError, WorkflowCancelAbort, QstashError checks) are thrown and caught at defined boundaries in serveBase, and unhandled errors are formatted into a structured JSON response rather than swallowed. tsconfig.json runs in strict mode, ESLint is configured with the TypeScript-ESLint recommended rule set, and prettier plus husky pre-commit hooks keep style consistent. Naming is consistent throughout (Lazy*Step, trigger*, handle*), and public APIs are documented with TSDoc comments carrying runnable examples. What Makes It Unique Rather than requiring a persistent worker process, a durable execution engine, or a dedicated orchestration server, Workflow-js encodes the entire execution log of a workflow into the request/response cycle with QStash, letting ordinary stateless serverless functions behave as long-running, resumable processes purely by replaying already-completed steps on each new invocation. Combined with context.call (which offloads a third-party HTTP call to QStash so the calling function isn’t billed for the wait) and context.invoke (which lets one workflow synchronously await the result of another), this gives it a distinctive request-driven durability model rather than the persistent-worker or database-checkpoint approaches used by comparable durable-execution frameworks.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search