A workflow’s context is a JavaScript object provided by the serve function and is used to define your workflow endpoint. This context object offers utility methods for creating workflow steps, managing delays, and performing timeout-resistant HTTP calls.

api/workflow/route.ts
import { serve } from "@upstash/workflow/nextjs"

export const { POST } = serve(
  // 👇 the workflow context
  async (context) => {
    // ...
  }
)

This context object provides utility methods to create workflow steps, wait for certain periods of time or perform timeout-resistant HTTP calls.

Further, the context object provides all request headers, the incoming request payload and current workflow ID.

Context Object Properties

  • qstashClient: QStash client used by the serve method

  • workflowRunId: Current workflow run ID

  • url: Publically accessible workflow endpoint URL

  • failureUrl: URL for workflow failure notifications.

  • requestPayload: Incoming request payload

  • rawInitialPayload: String version of the initial payload

  • headers: Request headers

  • env: Environment variables

Core Workflow Methods

context.run

Defines and executes a workflow step.

context.sleep

Pauses workflow execution for a specified duration.

Always await a sleep action to properly pause execution.

api/workflow/route.ts
import { serve } from "@upstash/workflow/nextjs"
import { signIn, sendEmail } from "@/utils/onboarding-utils"

export const { POST } = serve<User>(
  async (context) => {
    const userData = context.requestPayload;

    const user = await context.run("sign-in", async () => {
      const signedInUser = await signIn(userData);
      return signedInUser;
    });

    // 👇 Wait for one day (in seconds)
    await context.sleep("wait-until-welcome-email", 60 * 60 * 24);

    await context.run("send-welcome-email", async () => {
      return sendEmail(user.name, user.email);
    });
  },
);

context.sleepUntil

Pauses workflow execution until a specific timestamp.

Always await a sleepUntil action to properly pause execution.

api/workflow/route.ts
import { serve } from "@upstash/workflow/nextjs"
import { signIn, sendEmail } from "@/utils/onboarding-utils"

export const { POST } = serve<User>(async (context) => {
  const userData = context.requestPayload

  const user = await context.run("sign-in", async () => {
    return signIn(userData)
  })

  // 👇 Calculate the date for one week from now
  const oneWeekFromNow = new Date()
  oneWeekFromNow.setDate(oneWeekFromNow.getDate() + 7)

  // 👇 Wait until the calculated date
  await context.sleepUntil("wait-for-one-week", oneWeekFromNow)

  await context.run("send-welcome-email", async () => {
    return sendEmail(user.name, user.email)
  })
})

context.call

Performs an HTTP call as a workflow step, allowing for longer response times.

Can take up to 15 minutes or 2 hours, depending on your QStash plans max HTTP connection timeout.

import { serve } from "@upstash/workflow/nextjs"

export const { POST } = serve<{ topic: string }>(async (context) => {
  const request = context.requestPayload

  const {
    status, // response status
    headers, // response headers
    body // response body
  } = await context.call(
    "generate-long-essay", // Step name
    {
      url: "https://api.openai.com/v1/chat/completions", // Endpoint URL
      method: "POST",
      body: { // Request body
        model: "gpt-4o",
        messages: [
          {
            role: "system",
            content:
              "You are a helpful assistant writing really long essays that would cause a normal serverless function to timeout.",
          },
          { role: "user", content: request.topic },
        ],
      },
      headers: { // request headers
        authorization: `Bearer ${process.env.OPENAI_API_KEY}`,
      }
    }
  )
})

context.call attempts to parse the response body as JSON. If this is not possible, the body is returned as it is.

In TypeScript, you can declare the expected result type as follows:

type ResultType = {
  field1: string,
  field2: number
};

const result = await context.call<ResultType>( ... ); 

By default, context.call only retries once if its request to the given URL fails.

A limitation of context.call is that it does not support using localhost or certain QStash endpoints (such as the publish endpoint) as the destination URL. Attempting to send a request to these endpoints will result in context.call failing.

context.waitForEvent

Stops the workflow run until it is notified externally to continue.

There is also a timeout setting which makes the workflow continue if it’s not notified within the time frame.

import { serve } from "@upstash/workflow/nextjs"

export const { POST } = serve<{ topic: string }>(async (context) => {
  const request = context.requestPayload

  const {
    eventData, // data passed in notify
    timeout    // boolean denoting whether the step was notified or timed out
  } = await context.waitForEvent(
    "wait for some event",
    "my-event-id",
    1000 // 1000 second timeout
  );
})

A workflow run waiting for event can be notified in two ways:

context.notify

Notifies workflows waiting for an event with some payload.

import { serve } from "@upstash/workflow/nextjs"

export const { POST } = serve<{ topic: string }>(async (context) => {
  const payload = context.requestPayload

  const {
    notifyResponse // result of notify, which is a list of notified waiters
  } = await context.notify("notify step", "my-event-Id", payload);
})

notifyResponse is a list of NotifyResponse objects:

export type NotifyResponse = {
  waiter: Waiter;
  messageId: string;
  error: string;
};

More details about the Waiter object:

Waiter

Error handling and retries

  • context.run and context.call automatically retry on failures.
  • Default: 3 retries with exponential backoff.
  • Future releases will allow configuration of retry behavior.

Limitations and Plan-Specific-Features

  • Sleep durations and HTTP timeouts vary based on your QStash plan.
  • See your plan’s “Max Delay” and “Max HTTP Connection Timeout” for specific limits.