> ## Documentation Index
> Fetch the complete documentation index at: https://upstash.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Migration Guide

This guide covers migration between different versions of Upstash Workflow.

<AccordionGroup>
  <Accordion title="January 2026 - Major Release 1.0.0" defaultOpen>
    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

    1. Install the new package:

    ```bash theme={"system"}
    npm install @upstash/workflow-agents
    ```

    2. Update your imports:

    ```typescript theme={"system"}
    // 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](/workflow/agents/overview) 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:

    ```typescript theme={"system"}
    // 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:

    ```typescript theme={"system"}
    // 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.

    <Note>
      In the Python SDK, these options remain in the `serve` decorator as they were before.
    </Note>

    ## 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()`:

    ```typescript theme={"system"}
    // 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()`:

    ```typescript theme={"system"}
    // 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:

    ```typescript theme={"system"}
    // 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](/workflow/howto/middlewares) for details.

    ## Removed `onStepFinish`

    The `onStepFinish` callback has been removed. Use middlewares instead.

    ### Migration Steps

    Replace `onStepFinish` with a custom middleware:

    ```typescript theme={"system"}
    // 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](/workflow/howto/middlewares) for more details.
  </Accordion>

  <Accordion title="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:

    <Tabs>
      <Tab title="npm">
        ```bash theme={"system"}
        npm install @upstash/workflow
        ```
      </Tab>

      <Tab title="pnpm">
        ```bash theme={"system"}
        pnpm install @upstash/workflow
        ```
      </Tab>

      <Tab title="bun">
        ```bash theme={"system"}
        bun add @upstash/workflow
        ```
      </Tab>
    </Tabs>

    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:

    ```ts theme={"system"}
    // 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:

    ```javascript theme={"system"}
    // 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](/workflow/quickstarts/platforms) 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](/workflow/basics/context#context-call) in your workflow, you will need to change
    how it's called and what it returns. Here is what the change looks like:

    ```javascript theme={"system"}
    // 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](/workflow/howto/failures).

    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`.
  </Accordion>
</AccordionGroup>
