> ## 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.

# Realtime Quickstart

[**Upstash Realtime**](/realtime/overall/quickstart) lets you emit events from your workflow and subscribe to them in real-time on your frontend.

## How It Works

Upstash Realtime is powered by Upstash Redis and provides a clean, 100% type-safe API for publishing and subscribing to events:

* Your frontend can subscribe to events
* When you **emit** an event, it's instantly delivered to live subscribers on the frontend
* You can also replay events that happened in the past

This guide shows you how to integrate Upstash Workflow with Upstash Realtime to display real-time progress updates in your frontend.

## Setup

### 1. Install Packages

```bash theme={"system"}
npm install @upstash/workflow @upstash/realtime @upstash/redis zod
```

### 2. Configure Upstash Realtime

Create a Realtime instance in `lib/realtime.ts`:

```typescript title="lib/realtime.ts" theme={"system"}
import { InferRealtimeEvents, Realtime } from "@upstash/realtime";
import { Redis } from "@upstash/redis";
import z from "zod/v4";

const redis = Redis.fromEnv();

const schema = {
  workflow: {
    runFinish: z.object({}),
    stepFinish: z.object({
      stepName: z.string(),
      result: z.unknown().optional(),
    }),
  },
};

export const realtime = new Realtime({ schema, redis });
export type RealtimeEvents = InferRealtimeEvents<typeof realtime>;
```

### 3. Create a Realtime Middleware

Create a custom middleware that will emit events to Realtime at `lib/middleware.ts`:

```typescript title="lib/middleware.ts" theme={"system"}
import { WorkflowMiddleware } from "@upstash/workflow";
import { realtime } from "./realtime";

export const realtimeMiddleware = new WorkflowMiddleware({
  name: "realtime-events",
  callbacks: {
    afterExecution: async ({ context, stepName, result }) => {
      const channel = realtime.channel(context.workflowRunId);
      await channel.emit("workflow.stepFinish", {
        stepName,
        result,
      });
    },
    runCompleted: async ({ context }) => {
      const channel = realtime.channel(context.workflowRunId);
      await channel.emit("workflow.runFinish", {});
    },
  },
});
```

**Key points:**

* The `afterExecution` callback is triggered after each workflow step completes
* The `runCompleted` callback is triggered when the entire workflow finishes
* We use `context.workflowRunId` to create a unique channel for each workflow run
* Events are automatically emitted without needing to manually call emit inside your workflow steps

### 4. Create a Realtime Endpoint

Create an API route at `app/api/realtime/route.ts` to handle Realtime connections:

```typescript title="app/api/realtime/route.ts" theme={"system"}
import { handle } from "@upstash/realtime";
import { realtime } from "@/lib/realtime";

export const GET = handle({ realtime });
```

This endpoint enables Server-Sent Events (SSE) connections for real-time updates.

### 5. Add the Realtime Provider

Wrap your application in the `RealtimeProvider` by updating your root layout at `app/layout.tsx`:

```tsx title="app/layout.tsx" theme={"system"}
"use client";

import { RealtimeProvider } from "@upstash/realtime/client";

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html>
      <body>
        <RealtimeProvider>{children}</RealtimeProvider>
      </body>
    </html>
  );
}
```

### 6. Create a Typed Client Hook

Create a typed `useRealtime` hook at `lib/realtime-client.ts`:

```typescript title="lib/realtime-client.ts" theme={"system"}
"use client";

import { createRealtime } from "@upstash/realtime/client";
import type { RealtimeEvents } from "./realtime";

export const { useRealtime } = createRealtime<RealtimeEvents>();
```

***

## Building the Workflow

### 1. Create the Workflow Endpoint

Create your workflow at `app/api/workflow/route.ts`:

```typescript title="app/api/workflow/route.ts" theme={"system"}
import { serve } from "@upstash/workflow/nextjs";
import { realtimeMiddleware } from "@/lib/middleware";

type WorkflowPayload = {
  userId: string;
  action: string;
};

export const { POST } = serve<WorkflowPayload>(
  async (context) => {
    const { userId, action } = context.requestPayload;

    await context.run("validate-data", async () => {
      return { valid: true, userId, action };
    });

    await context.run("process-action", async () => {
      // Your business logic here
      return { processed: true, userId, action };
    });

    return { success: true, workflowRunId: context.workflowRunId };
  },
  {
    middlewares: [realtimeMiddleware],
  }
);
```

**Key points:**

* Import the `realtimeMiddleware` from `@/lib/middleware`
* Pass the middleware in the `middlewares` array to the `serve` function
* The middleware automatically emits events after each step and when the workflow completes

### 2. Create a Trigger Endpoint

Create an endpoint to trigger workflows at `app/api/trigger/route.ts`:

```typescript title="app/api/trigger/route.ts" theme={"system"}
import { NextRequest, NextResponse } from "next/server";
import { Client } from "@upstash/workflow";

export const workflowClient = new Client({
  token: process.env.QSTASH_TOKEN,
  baseUrl: process.env.QSTASH_URL,
});

export async function POST(request: NextRequest) {
  const workflowUrl = `${request.nextUrl.origin}/api/workflow`;

  const { workflowRunId } = await workflowClient.trigger({
    url: workflowUrl,
    body: {
      userId: "user-123",
      action: "process-data",
    },
  });

  return NextResponse.json({ workflowRunId });
}
```

***

## Building the Frontend

### 1. Create a Custom Hook

Create a React hook to manage the Realtime subscription at `hooks/useWorkflow.ts`:

```typescript theme={"system"}
"use client";

import { useRealtime } from "@/lib/realtime-client";
import { useState, useCallback } from "react";

interface WorkflowStep {
  stepName: string;
  result?: unknown;
}

export function useWorkflow() {
  const [workflowRunId, setWorkflowRunId] = useState<string | null>(null);
  const [steps, setSteps] = useState<WorkflowStep[]>([]);
  const [isRunFinished, setIsRunFinished] = useState(false);

  useRealtime({
    enabled: Boolean(workflowRunId),
    channels: workflowRunId ? [workflowRunId] : [],
    events: ["workflow.stepFinish", "workflow.runFinish"],
    onData({ event, data }) {
      if (event === "workflow.stepFinish") {
        setSteps((prev) => [...prev, data]);
      }

      if (event === "workflow.runFinish") {
        setIsRunFinished(true);
      }
    },
  });

  const trigger = () => {
    setSteps([]);
    setIsRunFinished(false);

    const response = await fetch("/api/trigger", {
      method: "POST",
    });

    const data = await response.json();
    setWorkflowRunId(data.workflowRunId);
  };

  return {
    trigger,
    workflowRunId,
    steps,
    isRunFinished,
  };
}
```

**Key features:**

* Subscribe to multiple events using the `events` array: `["workflow.stepFinish", "workflow.runFinish"]`
* The hook manages both triggering the workflow and subscribing to updates
* Type-safe event handling with TypeScript

### 2. Use the Hook in Your Component

```tsx theme={"system"}
"use client";

import { useWorkflow } from "@/hooks/useWorkflow";

export default function WorkflowPage() {
  const { trigger, steps, isRunFinished } = useWorkflow();

  return (
    <div>
      <button onClick={trigger}>Click to Trigger Workflow</button>

      {isRunFinished && <p>✅ Workflow Finished!</p>}

      <p>Workflow Steps:</p>

      {steps.map((step, index) => (
        <div key={index}>
          <strong>{step.stepName}</strong>
          {Boolean(step.result) && <span>: {JSON.stringify(step.result)}</span>}
        </div>
      ))}
    </div>
  );
}
```

## How It All Works Together

1. **User triggers workflow**: The frontend calls `/api/trigger`, which returns a `workflowRunId`
2. **Frontend subscribes**: Using the `workflowRunId`, the frontend subscribes to the Realtime channel
3. **Workflow executes**: The workflow runs as a background job, emitting events at each step
4. **Real-time updates**: As the workflow emits events, they're instantly delivered to the frontend via Server-Sent Events

## Full Example

For a complete working example with all steps, error handling, and UI components, check out the [Upstash Realtime example on GitHub](https://github.com/upstash/workflow-js/tree/main/examples/upstash-realtime).

## Next Steps

* Learn about [human-in-the-loop workflows with Realtime](./human-in-the-loop)
* Explore [Realtime features](/realtime/overall/quickstart)
* Check out [Workflow configuration options](/workflow/howto/configure)
