Migration Guide
This guide covers migration between different versions of Upstash Workflow.
January 2026 - Major Release 1.0.0
In January 2026, we released 1.0.0 version of the TypeScript SDK with several breaking changes to improve the developer experience, reduce bundle size, and simplify configuration.
Agents API → Separate Package
The Agents API has been moved to a separate package to remove the AI SDK dependency from the core workflow package.
Migration Steps
- Install the new package:
npm install @upstash/workflow-agents- Update your imports:
// Oldimport { serve } from "@upstash/workflow/nextjs";export const { POST } = serve(async (context) => { const model = context.agents.openai('gpt-3.5-turbo'); const agent = context.agents.agent({ ... }); const task = context.agents.task({ ... });});// Newimport { serve } from "@upstash/workflow/nextjs";import { agentWorkflow } from "@upstash/workflow-agents";export const { POST } = serve(async (context) => { const agents = agentWorkflow(context) const model = agents.openai('gpt-3.5-turbo'); const agent = agents.agent({ ... }); const task = agents.task({ ... });});See Agents documentation for more details.
Removed keepTriggerConfig and useFailureFunction
These parameters are no longer needed in client.trigger() as both are now true by default.
Migration Steps
Simply remove these parameters from your trigger calls:
// Oldconst { workflowRunId } = await client.trigger({ url: "https://your-app.com/api/workflow", retries: 3, keepTriggerConfig: true, useFailureFunction: true});// Newconst { workflowRunId } = await client.trigger({ url: "https://your-app.com/api/workflow", retries: 3});Configuration passed to trigger() now automatically applies to the entire workflow.
Configuration Moved from serve to trigger
The retries, flowControl, retryDelay, and failureUrl options have been removed from serve() and should now be passed in client.trigger().
Migration Steps
Move configuration from serve options to trigger:
// Oldexport const { POST } = serve( async (context) => { ... }, { retries: 3, retryDelay: "1000 * (1 + retried)", flowControl: { key: "my-key", rate: 10 } });// Trigger callawait client.trigger({ url: "..." });// Newexport const { POST } = serve( async (context) => { ... } // No configuration here anymore);// Configuration in trigger callawait client.trigger({ url: "...", retries: 3, retryDelay: "1000 * (1 + retried)", flowControl: { key: "my-key", rate: 10 }});This change makes it easier to configure different behavior for different workflow runs of the same endpoint.
In the Python SDK, these options remain in the serve decorator as they were before.
Removed stringifyBody from context.call and context.invoke
The stringifyBody parameter has been removed. The body parameter now expects a string.
Migration Steps
Update your call and invoke methods to use JSON.stringify():
// Oldconst result = await context.call("call-api", { url: "https://api.example.com/endpoint", method: "POST", body: { key: "value" }, stringifyBody: true});// Newconst result = await context.call("call-api", { url: "https://api.example.com/endpoint", method: "POST", body: JSON.stringify({ key: "value" })});The same applies to context.invoke():
// Oldawait context.invoke("invoke-workflow", { workflow: otherWorkflow, body: { key: "value" }, stringifyBody: true});// Newawait context.invoke("invoke-workflow", { workflow: otherWorkflow, body: JSON.stringify({ key: "value" })});Logger → Middleware System
The logging system has been replaced with a more flexible middleware system.
Migration Steps
Replace the old logger with the new middleware:
// Old// Logging was automatic or controlled via verbose optionexport const { POST } = serve( async (context) => { ... }, { verbose: true });// Newimport { loggingMiddleware } from "@upstash/workflow";export const { POST } = serve( async (context) => { ... }, { middlewares: [loggingMiddleware] });You can also create custom middlewares for more control. See Middlewares documentation for details.
Removed onStepFinish
The onStepFinish callback has been removed. Use middlewares instead.
Migration Steps
Replace onStepFinish with a custom middleware:
// Oldexport const { POST } = serve( async (context) => { ... }, { onStepFinish: (stepName, result) => { console.log(`Step ${stepName} finished with:`, result); } });// Newimport { WorkflowMiddleware } from "@upstash/workflow";const stepFinishMiddleware = new WorkflowMiddleware({ name: "step-finish", callbacks: { afterExecution: async ({ stepName, result }) => { console.log(`Step ${stepName} finished with:`, result); } }});export const { POST } = serve( async (context) => { ... }, { middlewares: [stepFinishMiddleware] });See Middlewares documentation for more details.
October 2024 - Migrate from @upstash/qstash
In October 2024, we released a new SDK, @upstash/workflow, for Upstash Workflow, separating its development from the QStash SDK. Although Upstash Workflow is built on QStash, our goal is to improve the developer experience and support with a dedicated SDK. Development for Upstash Workflow will occur in @upstash/workflow, and Workflow-related imports will be removed from @upstash/qstash in future releases.
If you started using Upstash Workflow with @upstash/qstash, you will need to migrate to @upstash/workflow. We have made some backward-incompatible changes, but we aim to make the transition as smooth as possible.
In this guide, we will explain the changes you may need to make for migration.
Install @upstash/workflow
First, we will need to install the new package with:
npm install @upstash/workflowpnpm install @upstash/workflowbun add @upstash/workflowIf you were using @upstash/qstash only for workflow, you can uninstall it from your project.
Serve methods
You will need to change the imports from @upstash/qstash to @upstash/workflow:
// oldimport { serve } from "@upstash/qstash/nextjs"// new import { serve } from "@upstash/workflow/nextjs"We have updated what our serve methods return. We made this change to make it
easier to extend the API in the future.
For instance, Next.js method changed like this:
// oldexport const POST = serve(...);// newexport const { POST } = serve(...);We kept the serve method of Hono the same. The rest are updated in a similar way.
See the quickstarts for the new way serve
should be used.
Additionally, @upstash/workflow/nuxt import is removed. You should use @upstash/workflow/h3
instead. This change was made because nuxt uses h3 under the hood and our serve method
for nuxt can work with any project using h3.
Updating context.call
If you were using context.call method in your workflow, you will need to change
how it's called and what it returns. Here is what the change looks like:
// oldconst result = await context.call("call step", "<call-url>", "POST", ...)// newconst { status, // response status headers, // response headers body // response body} = await context.call("call step", { url: "<call-url>", method: "POST", ...})In the old version, we only returned the response body. Also, if the request to the url failed, the workflow run would fail.
In the new version, we update how the parameters are passed to the context.call.
Additionally, we change the fail behavior: if the request fails, it doesn't make the
workflow fail. Instead, the status and the body is simply returned and workflow
continues as usual.
If you have ongoing workflow runs which call context.call during your transition,
status and headers fields may not be available in these old runs. After your
transition, all workflow runs will have all three fields.
Renaming Errors
The errors in Workflow were renamed from QStashWorkflowError and QStashWorkflowAbort to WorkflowError and WorkflowAbort.