Skip to main content
Normally, workflows are created with serve(), which exposes each workflow as its own HTTP endpoint. If workflows were invoked only by their full URL, it would mean:
  • You’d have to provide the URL explicitly like a trigger request
  • You’d lose type safety for request and response payloads
To avoid these issues, Upstash Workflow lets you define workflows as objects and expose them under the same parent path. This way, you can invoke a workflow simply by passing the object to context.invoke, with full type safety and no URLs required.
1

Create workflow objects

Use createWorkflow() to define workflows as objects.It works just like serve()—accepting the same arguments—but does not expose the workflow directly as an HTTP endpoint. Instead, it simply initializes a workflow object.
const anotherWorkflow = createWorkflow(
  // 👇 Request Payload Type
  async (context: WorkflowContext<string>) => {

    await context.sleep("wait 1 second", 1)

    // 👇 Workflow Response Type
    return { message: "This is the data returned by the workflow" };
  }
);

const someWorkflow = createWorkflow(async (context) => {
  // 👇 Invoke the workflow with type-safe call
  const { body } = await context.invoke(
    "invoke anotherWorkflow",
    {
      workflow: anotherWorkflow,
      body: "user-1"
    }
  ),
});
2

Expose multiple workflows under same endpoint

Use serveMany() instead of serve() to expose multiple workflows on a single catch‑all route.If one workflow is going to invoke another, both must be included in the same serveMany definition. First step of using serveMany is to define a catch-all route.
app/serve-many/[...any]/route.ts
export const { POST } = serveMany(
  {
    "workflow-one-route": workflowOne,
    "workflow-two-route": workflowTwo,
  }
)
In Next.js, a catch‑all route can be defined by creating a route.ts file inside a directory named with [...], for example: app/serve-many/[...any]/route.ts.For implementations of serveMany in other frameworks, you can refer to the projects available in the examples directory of the workflow-js repository.
3

Invoke by passing workflow object

When invoking, pass the workflow object created with createWorkflow() (from step 1) as the argument to context.invoke(). This removes the need to specify a URL explicitly and ensures the call is fully type‑safe.
const someWorkflow = createWorkflow(async (context) => {
  // 👇 Invoke the workflow with type-safe call
  const { body } = await context.invoke(
    "invoke anotherWorkflow",
    {
      // 👇 Pass the workflow object as argument
      workflow: anotherWorkflow,
      body: "user-1"
    }
  ),
});
4

Trigger

In this example, both workflowOne and workflowTwo are exposed through serveMany, sharing the same parent path.You can start workflowOne by sending a trigger request to: https://your-app/serve-many/workflow-one-route.
import { Client } from "@upstash/workflow";

const client = new Client({ token: "<QSTASH_TOKEN>" })

const { workflowRunId } = await client.trigger({
  // 👇 URL of workflow one
  url: "https://your-app/serve-many/workflow-one-route",
  applyConfiguration: true,
})
Route names are inferred from the keys you pass to serveMany. For example, you can start workflowTwo by sending trigger request to: https://your-app/serve-many/workflow-two-route.