trigger-realtime

SkillProductivity

Teaches your agent how to add realtime updates to Trigger.dev tasks and frontend apps.

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the trigger-realtime skill

About this capability

How to use realtime in your Trigger.dev tasks and your frontend

What this skill tells your AI

The instructions your AI receives, as published by trycompai/comp in .agents/skills/trigger-realtime/SKILL.md and read by ahel’s review.

Source Cursor rule: .cursor/rules/trigger.realtime.mdc. Original file scope: **/trigger/**/*.ts. Original Cursor alwaysApply: false.

Trigger.dev Realtime (v4)

Real-time monitoring and updates for runs

Core Concepts

Realtime allows you to:

  • Subscribe to run status changes, metadata updates, and streams
  • Build real-time dashboards and UI updates
  • Monitor task progress from frontend and backend

Authentication

Public Access Tokens

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

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

Trigger Tokens (Frontend only)

// Single-use token for triggering tasks
const triggerToken = await auth.createTriggerPublicToken("my-task", {
  expirationTime: "30m",
});

Backend Usage

Subscribe to Runs

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

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

// Subscribe to specific run
for await (const run of runs.subscribeToRun<typeof myTask>(handle.id)) {
  console.log(`Status: ${run.status}, Progress: ${run.metadata?.progress}`);
  if (run.status === "COMPLETED") break;
}

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

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

Streams

import { task, metadata } from "@trigger.dev/sdk";

// Task that streams data
export type STREAMS = {
  openai: OpenAI.ChatCompletionChunk;
};

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

    // Register stream
    const stream = await metadata.stream("openai", completion);

    let text = "";
    for await (const chunk of stream) {
      text += chunk.choices[0]?.delta?.content || "";
    }

    return { text };
  },
});

// Subscribe to streams
for await (const part of runs.subscribeToRun(runId).withStreams<STREAMS>()) {
  switch (part.type) {
    case "run":
      console.log("Run update:", part.run.status);
      break;
    case "openai":
      console.log("Stream chunk:", part.chunk);
      break;
  }
}

React Frontend Usage

Installation

bun add @trigger.dev/react-hooks

Triggering Tasks

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

function TriggerComponent({ accessToken }: { accessToken: string }) {
  // Basic trigger
  const { submit, handle, isLoading } = useTaskTrigger<typeof myTask>("my-task", {
    accessToken,
  });

  // Trigger with realtime updates
  const {
    submit: realtimeSubmit,
    run,
    isLoading: isRealtimeLoading,
  } = useRealtimeTaskTrigger<typeof myTask>("my-task", { accessToken });

  return (
    <div>
      <button onClick={() => submit({ data: "value" })} disabled={isLoading}>
        Trigger Task
      </button>

      <button onClick={() => realtimeSubmit({ data: "realtime" })} disabled={isRealtimeLoading}>
        Trigger with Realtime
      </button>

      {run && <div>Status: {run.status}</div>}
    </div>
  );
}

Subscribing to Runs

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

function SubscribeComponent({ runId, accessToken }: { runId: string; accessToken: string }) {
  // Subscribe to specific run
  const { run, error } = useRealtimeRun<typeof myTask>(runId, {
    accessToken,
    onComplete: (run) => {
      console.log("Task completed:", run.output);
    },
  });

  // Subscribe to tagged runs
  const { runs } = useRealtimeRunsWithTag("user-123", { accessToken });

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

  return (
    <div>
      <div>Status: {run.status}</div>
      <div>Progress: {run.metadata?.progress || 0}%</div>
      {run.output && <div>Result: {JSON.stringify(run.output)}</div>}

      <h3>Tagged Runs:</h3>
      {runs.map((r) => (
        <div key={r.id}>
          {r.id}: {r.status}
        </div>
      ))}
    </div>
  );
}

Streams with React

"use client";
import { useRealtimeRunWithStreams } from "@trigger.dev/react-hooks";
import type { streamingTask, STREAMS } from "../trigger/tasks";

function StreamComponent({ runId, accessToken }: { runId: string; accessToken: string }) {
  const { run, streams } = useRealtimeRunWithStreams<typeof streamingTask, STREAMS>(runId, {
    accessToken,
  });

  const text = streams.openai
    .filter((chunk) => chunk.choices[0]?.delta?.content)
    .map((chunk) => chunk.choices[0].delta.content)
    .join("");

  return (
    <div>
      <div>Status: {run?.status}</div>
      <div>Streamed Text: {text}</div>
    </div>
  );
}

Wait Tokens

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

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

  return <button onClick={() => complete({ approved: true })}>Approve Task</button>;
}

SWR Hooks (Fetch Once)

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

function SWRComponent({ runId, accessToken }: { runId: string; accessToken: string }) {
  const { run, error, isLoading } = useRun<typeof myTask>(runId, {
    accessToken,
    refreshInterval: 0, // Disable polling (recommended)
  });

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

  return <div>Run: {run?.status}</div>;
}

Run Object Properties

Key properties available in run subscriptions:

  • id: Unique run identifier
  • status: QUEUED, EXECUTING, COMPLETED, FAILED, CANCELED, etc.
  • payload: Task input data (typed)
  • output: Task result (typed, when completed)
  • metadata: Real-time updatable data
  • createdAt, updatedAt: Timestamps
  • costInCents: Execution cost

Best Practices

  • Use Realtime over SWR: Recommended for most use cases due to rate limits
  • Scope tokens properly: Only grant necessary read/trigger permissions
  • Handle errors: Always check for errors in hooks and subscriptions
  • Type safety: Use task types for proper payload/output typing
  • Cleanup subscriptions: Backend subscriptions auto-complete, frontend hooks auto-cleanup

Signals

GitHub stars
2k
Forks
412
Last commit
Sep 2026
Advanced
Catalog kind
skill
Gateway key
trigger-realtime
Source
github.com/trycompai/comp