Skip to main content
This guide covers migration between different versions of Upstash Workflow.

January 2026 - Major Release

In January 2026, we released a major 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

  1. Install the new package:
npm install @upstash/workflow-agents
  1. Update your imports:
// Old
import { 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({ ... });
});

// New
import { 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:
// Old
const { workflowRunId } = await client.trigger({
  url: "https://your-app.com/api/workflow",
  retries: 3,
  keepTriggerConfig: true,
  useFailureFunction: true
});

// New
const { 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:
// Old
export const { POST } = serve(
  async (context) => { ... },
  {
    retries: 3,
    retryDelay: "1000 * (1 + retried)",
    flowControl: { key: "my-key", rate: 10 }
  }
);

// Trigger call
await client.trigger({ url: "..." });

// New
export const { POST } = serve(
  async (context) => { ... }
  // No configuration here anymore
);

// Configuration in trigger call
await 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():
// Old
const result = await context.call("call-api", {
  url: "https://api.example.com/endpoint",
  method: "POST",
  body: { key: "value" },
  stringifyBody: true
});

// New
const 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():
// Old
await context.invoke("invoke-workflow", {
  workflow: otherWorkflow,
  body: { key: "value" },
  stringifyBody: true
});

// New
await 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 option
export const { POST } = serve(
  async (context) => { ... },
  { verbose: true }
);

// New
import { 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:
// Old
export const { POST } = serve(
  async (context) => { ... },
  {
    onStepFinish: (stepName, result) => {
      console.log(`Step ${stepName} finished with:`, result);
    }
  }
);

// New
import { 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.
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/workflow
If 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:
// old
import { 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:
// old
export const POST = serve(...);

// new
export 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:
// old
const result = await context.call("call step", "<call-url>", "POST", ...)

// new
const {
  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.