home / skills / triggerdotdev / skills / trigger-realtime

trigger-realtime skill

/trigger-realtime

This skill enables real-time subscription and streaming of Trigger.dev task runs from frontend and backend to power live dashboards and progress indicators.

npx playbooks add skill triggerdotdev/skills --skill trigger-realtime

Review the files below or copy the command above to add this skill to your agents.

Files (2)
SKILL.md
6.6 KB
---
name: trigger-realtime
description: Subscribe to Trigger.dev task runs in real-time from frontend and backend. Use when building progress indicators, live dashboards, streaming AI/LLM responses, or React components that display task status.
---

# Trigger.dev Realtime

Subscribe to task runs and stream data in real-time from frontend and backend.

## When to Use

- Building progress indicators for long-running tasks
- Creating live dashboards showing task status
- Streaming AI/LLM responses to the UI
- React components that trigger and monitor tasks
- Waiting for user approval in tasks

## Authentication

### Create Public Access Token (Backend)

```ts
import { auth } from "@trigger.dev/sdk";

// Read-only token for specific runs
const publicToken = await auth.createPublicToken({
  scopes: {
    read: {
      runs: ["run_123"],
      tasks: ["my-task"],
    },
  },
  expirationTime: "1h",
});

// Pass this token to your frontend
```

### Create Trigger Token (for frontend triggering)

```ts
const triggerToken = await auth.createTriggerPublicToken("my-task", {
  expirationTime: "30m",
});
```

## Backend Subscriptions

```ts
import { runs, tasks } from "@trigger.dev/sdk";

// Trigger and subscribe
const handle = await tasks.trigger("my-task", { data: "value" });

for await (const run of runs.subscribeToRun(handle.id)) {
  console.log(`Status: ${run.status}`);
  console.log(`Progress: ${run.metadata?.progress}`);
  
  if (run.status === "COMPLETED") {
    console.log("Output:", run.output);
    break;
  }
}

// Subscribe to tagged runs
for await (const run of runs.subscribeToRunsWithTag("user-123")) {
  console.log(`Run ${run.id}: ${run.status}`);
}

// Subscribe to batch
for await (const run of runs.subscribeToBatch(batchId)) {
  console.log(`Batch run ${run.id}: ${run.status}`);
}
```

## React Hooks

### Installation

```bash
npm add @trigger.dev/react-hooks
```

### Trigger Task from React

```tsx
"use client";
import { useRealtimeTaskTrigger } from "@trigger.dev/react-hooks";
import type { myTask } from "../trigger/tasks";

function TaskTrigger({ accessToken }: { accessToken: string }) {
  const { submit, run, isLoading } = useRealtimeTaskTrigger<typeof myTask>(
    "my-task",
    { accessToken }
  );

  return (
    <div>
      <button 
        onClick={() => submit({ data: "value" })} 
        disabled={isLoading}
      >
        Start Task
      </button>
      
      {run && (
        <div>
          <p>Status: {run.status}</p>
          <p>Progress: {run.metadata?.progress}%</p>
          {run.output && <p>Result: {JSON.stringify(run.output)}</p>}
        </div>
      )}
    </div>
  );
}
```

### Subscribe to Existing Run

```tsx
"use client";
import { useRealtimeRun } from "@trigger.dev/react-hooks";
import type { myTask } from "../trigger/tasks";

function RunStatus({ runId, accessToken }: { runId: string; accessToken: string }) {
  const { run, error } = useRealtimeRun<typeof myTask>(runId, {
    accessToken,
    onComplete: (run) => {
      console.log("Completed:", run.output);
    },
  });

  if (error) return <div>Error: {error.message}</div>;
  if (!run) return <div>Loading...</div>;

  return (
    <div>
      <p>Status: {run.status}</p>
      <p>Progress: {run.metadata?.progress || 0}%</p>
    </div>
  );
}
```

### Subscribe to Tagged Runs

```tsx
"use client";
import { useRealtimeRunsWithTag } from "@trigger.dev/react-hooks";

function UserTasks({ userId, accessToken }: { userId: string; accessToken: string }) {
  const { runs } = useRealtimeRunsWithTag(`user-${userId}`, { accessToken });

  return (
    <ul>
      {runs.map((run) => (
        <li key={run.id}>{run.id}: {run.status}</li>
      ))}
    </ul>
  );
}
```

## Realtime Streams (AI/LLM)

### Define Stream (shared location)

```ts
// trigger/streams.ts
import { streams } from "@trigger.dev/sdk";

export const aiStream = streams.define<string>({
  id: "ai-output",
});
```

### Pipe Stream in Task

```ts
import { task } from "@trigger.dev/sdk";
import { aiStream } from "./streams";

export const streamingTask = task({
  id: "streaming-task",
  run: async (payload: { prompt: string }) => {
    const completion = await openai.chat.completions.create({
      model: "gpt-4",
      messages: [{ role: "user", content: payload.prompt }],
      stream: true,
    });

    const { waitUntilComplete } = aiStream.pipe(completion);
    await waitUntilComplete();
  },
});
```

### Read Stream in React

```tsx
"use client";
import { useRealtimeStream } from "@trigger.dev/react-hooks";
import { aiStream } from "../trigger/streams";

function AIResponse({ runId, accessToken }: { runId: string; accessToken: string }) {
  const { parts, error } = useRealtimeStream(aiStream, runId, {
    accessToken,
    throttleInMs: 50,
  });

  if (error) return <div>Error: {error.message}</div>;
  if (!parts) return <div>Waiting for response...</div>;

  return <div>{parts.join("")}</div>;
}
```

## Wait Tokens (Human-in-the-loop)

### In Task

```ts
import { task, wait } from "@trigger.dev/sdk";

export const approvalTask = task({
  id: "approval-task",
  run: async (payload) => {
    // Process initial data
    const processed = await processData(payload);

    // Wait for human approval
    const approval = await wait.forToken<{ approved: boolean }>({
      token: `approval-${payload.id}`,
      timeoutInSeconds: 86400, // 24 hours
    });

    if (approval.approved) {
      return await finalizeData(processed);
    }
    
    throw new Error("Not approved");
  },
});
```

### Complete Token from React

```tsx
"use client";
import { useWaitToken } from "@trigger.dev/react-hooks";

function ApprovalButton({ tokenId, accessToken }: { tokenId: string; accessToken: string }) {
  const { complete } = useWaitToken(tokenId, { accessToken });

  return (
    <div>
      <button onClick={() => complete({ approved: true })}>
        Approve
      </button>
      <button onClick={() => complete({ approved: false })}>
        Reject
      </button>
    </div>
  );
}
```

## Run Object Properties

| Property | Description |
|----------|-------------|
| `id` | Unique run identifier |
| `status` | `QUEUED`, `EXECUTING`, `COMPLETED`, `FAILED`, `CANCELED` |
| `payload` | Task input (typed) |
| `output` | Task result (typed, when completed) |
| `metadata` | Real-time updatable data |
| `createdAt` | Start timestamp |
| `costInCents` | Execution cost |

## Best Practices

1. **Scope tokens narrowly** — only grant necessary permissions
2. **Set expiration times** — don't use long-lived tokens
3. **Use typed hooks** — pass task types for proper inference
4. **Handle errors** — always check for errors in hooks
5. **Throttle streams** — use `throttleInMs` to control re-renders

See `references/realtime.md` for complete documentation.

Overview

This skill lets you subscribe to Trigger.dev task runs and stream live updates to frontend and backend clients. It supports real-time progress, streaming AI/LLM responses, run status subscriptions, and human-in-the-loop wait tokens. Use it to build responsive UIs and durable background workflows that report live state.

How this skill works

On the backend you create short-lived public or trigger tokens with scoped read/trigger permissions and pass them to clients. Subscribe to runs, batches, or tagged runs using iterator-style streams on the server or React hooks on the client to receive status, metadata, progress, and output as events. For streaming AI responses, define streams in shared code, pipe streaming completions into those streams in tasks, and read parts with realtime stream hooks in the UI.

When to use it

  • Showing progress indicators for long-running background tasks
  • Building live dashboards that track task status across users
  • Streaming partial AI/LLM outputs to the UI as they arrive
  • React components that trigger tasks and display live updates
  • Implementing human approval flows with wait tokens

Best practices

  • Scope tokens narrowly — grant only the minimal read/trigger permissions required
  • Set short expiration times for public/trigger tokens to reduce risk
  • Use typed hooks and task types to get compile-time inference for payloads/output
  • Always handle errors and loading states returned by hooks
  • Throttle realtime stream updates (throttleInMs) to control re-renders and UI performance

Example use cases

  • A React page that starts a data-processing job and shows percent-complete until output is ready
  • A live operations dashboard subscribing to runs tagged with a user or project ID
  • Streaming chat/assistant responses from an LLM into a conversation UI using defined streams
  • A human-in-the-loop approval flow where the task waits for a UI-completed token
  • Batch-run monitoring where backend subscribers log or react to each run completion

FAQ

How do I securely expose run data to the frontend?

Create a scoped public token on the backend with read permissions for specific runs or tasks and a short expiration, then pass that token to the client.

Can I stream partial AI responses to multiple clients?

Yes — define a stream in shared code, pipe the LLM completion into the stream in your task, and use realtime stream hooks on clients to subscribe to parts.