Skip to main content
The @trigger.dev/react-hooks package provides React hooks that connect your UI to the Trigger.dev Realtime API. You can trigger tasks, subscribe to run status, and receive live stream chunks — all without managing WebSocket connections or polling logic yourself.

Installation

npm add @trigger.dev/react-hooks

Authentication

All hooks require a Public Access Token passed via the accessToken option. Generate one server-side when you trigger a task (the run handle already includes one) or create a custom-scoped token with auth.createPublicToken().
import { useRealtimeRun } from "@trigger.dev/react-hooks";

export function RunStatus({ runId, publicAccessToken }: { runId: string; publicAccessToken: string }) {
  const { run, error } = useRealtimeRun(runId, {
    accessToken: publicAccessToken,
    // baseURL is only needed for self-hosted Trigger.dev instances
    // baseURL: "https://your-trigger-dev-instance.com",
  });

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

  return <p>Status: {run.status}</p>;
}
Never expose your secret API key to the browser. Public Access Tokens are short-lived and scoped to specific runs or tags. Generate them in a server route or API handler and pass them to the client.

Hooks reference

useTaskTrigger

Trigger a task from a React component. Returns a submit function, a loading flag, the run handle, and any submission error.
"use client";
import { useTaskTrigger } from "@trigger.dev/react-hooks";
import type { myTask } from "../trigger/my-task";

export function TriggerButton({ accessToken }: { accessToken: string }) {
  const { submit, isLoading, handle, error } = useTaskTrigger<typeof myTask>("my-task", {
    accessToken,
  });

  return (
    <div>
      <button onClick={() => submit({ message: "hello" })} disabled={isLoading}>
        {isLoading ? "Triggering..." : "Run task"}
      </button>
      {handle && <p>Run ID: {handle.id}</p>}
      {error && <p>Error: {error.message}</p>}
    </div>
  );
}
Returns:
PropertyTypeDescription
submit(payload, options?) => voidTrigger the task with a typed payload
isLoadingbooleantrue while the trigger request is in-flight
handleRunHandle | undefinedThe run handle returned after a successful trigger
errorError | undefinedAny error from the trigger request

useRun

Fetch a run’s current state once (or poll with refreshInterval). Uses SWR under the hood.
"use client";
import { useRun } from "@trigger.dev/react-hooks";
import type { myTask } from "../trigger/my-task";

export function RunDetails({ runId, accessToken }: { runId: string; accessToken: string }) {
  const { run, isLoading, error } = useRun<typeof myTask>(runId, { accessToken });

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

  return (
    <div>
      <p>Status: {run.status}</p>
      {run.output && <pre>{JSON.stringify(run.output, null, 2)}</pre>}
    </div>
  );
}
Returns:
PropertyTypeDescription
runRetrieveRunResult | undefinedThe fetched run object
isLoadingbooleantrue on the first fetch
isValidatingbooleantrue while re-fetching in the background
isErrorbooleantrue if an error occurred
errorError | undefinedThe error, if any

useRealtimeRun

Subscribe to live updates for a single run. The run object is updated automatically whenever the run’s status, metadata, or tags change.
"use client";
import { useRealtimeRun } from "@trigger.dev/react-hooks";
import type { myTask } from "../trigger/my-task";

export function LiveRunStatus({ runId, accessToken }: { runId: string; accessToken: string }) {
  const { run, error, stop } = useRealtimeRun<typeof myTask>(runId, {
    accessToken,
    onComplete: (run, err) => {
      console.log("Run finished:", run.status, err);
    },
  });

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

  return (
    <div>
      <p>Status: {run.status}</p>
      {run.finishedAt && (
        <p>Finished at: {new Date(run.finishedAt).toLocaleTimeString()}</p>
      )}
      <button onClick={stop}>Stop watching</button>
    </div>
  );
}
Options:
OptionTypeDefaultDescription
accessTokenstringrequiredPublic Access Token
enabledbooleantrueSet to false to pause the subscription
stopOnCompletionbooleantrueStop the SSE connection when the run finishes
onComplete(run, err?) => voidCalled once when the run reaches a terminal state
throttleInMsnumber16Minimum interval between state updates
Returns:
PropertyTypeDescription
runRealtimeRun | undefinedThe live run object
errorError | undefinedAny subscription error
stop() => voidManually close the SSE connection

useRealtimeRunWithStreams

Subscribe to live run updates and receive stream chunks from your task. This is the primary hook for displaying LLM output in real-time.
"use client";
import { useRealtimeRunWithStreams } from "@trigger.dev/react-hooks";
import type { chatTask } from "../trigger/chat";
import type OpenAI from "openai";

type Streams = {
  default: OpenAI.Chat.ChatCompletionChunk;
};

export function ChatOutput({ runId, accessToken }: { runId: string; accessToken: string }) {
  const { run, streams, error } = useRealtimeRunWithStreams<typeof chatTask, Streams>(runId, {
    accessToken,
  });

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

  if (error) return <p>Error: {error.message}</p>;

  return (
    <div>
      <p>{text}</p>
      {run?.status === "COMPLETED" && <span>Generation complete</span>}
    </div>
  );
}
Returns:
PropertyTypeDescription
runRealtimeRun | undefinedThe live run object
streams{ [key: string]: Array<T> }Accumulated stream chunks keyed by stream name
errorError | undefinedAny subscription error
stop() => voidManually close the connection

useRealtimeTaskTrigger

Combines useTaskTrigger and useRealtimeRun into a single hook. Trigger a task and immediately begin watching it — no need to thread the run ID manually.
"use client";
import { useRealtimeTaskTrigger } from "@trigger.dev/react-hooks";
import type { myTask } from "../trigger/my-task";

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

  return (
    <div>
      <button onClick={() => submit({ message: "go" })} disabled={isLoading}>
        {isLoading ? "Starting..." : "Start"}
      </button>
      {run && <p>Status: {run.status}</p>}
      {error && <p>Error: {error.message}</p>}
    </div>
  );
}

useRealtimeTaskTriggerWithStreams

Combines triggering, live run state, and stream chunks into one hook.
"use client";
import { useRealtimeTaskTriggerWithStreams } from "@trigger.dev/react-hooks";
import type { chatTask } from "../trigger/chat";
import type OpenAI from "openai";

type Streams = { default: OpenAI.Chat.ChatCompletionChunk };

export function ChatInterface({ accessToken }: { accessToken: string }) {
  const { submit, isLoading, run, streams, error } =
    useRealtimeTaskTriggerWithStreams<typeof chatTask, Streams>("chat", { accessToken });

  const text = (streams.default ?? [])
    .map((c) => c.choices[0]?.delta?.content ?? "")
    .join("");

  return (
    <div>
      <button
        onClick={() => submit({ prompt: "Explain quantum entanglement" })}
        disabled={isLoading || (run && !run.finishedAt)}
      >
        Ask
      </button>
      <p>{text || (run ? "Generating..." : "Press Ask to start")}</p>
      {error && <p>Error: {error.message}</p>}
    </div>
  );
}

useRealtimeRunsWithTag

Subscribe to all runs that share a tag. Useful for tracking all work associated with a user, session, or job.
"use client";
import { useRealtimeRunsWithTag } from "@trigger.dev/react-hooks";

export function UserJobs({ userId, accessToken }: { userId: string; accessToken: string }) {
  const { runs, error } = useRealtimeRunsWithTag(`user:${userId}`, {
    accessToken,
    createdAt: "1h", // Only runs created in the last hour
  });

  if (error) return <p>Error: {error.message}</p>;

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

useRealtimeBatch

Subscribe to all runs within a batch triggered with tasks.batchTrigger().
"use client";
import { useRealtimeBatch } from "@trigger.dev/react-hooks";

export function BatchProgress({ batchId, accessToken }: { batchId: string; accessToken: string }) {
  const { runs, error } = useRealtimeBatch(batchId, { accessToken });

  const completed = runs.filter((r) => r.finishedAt).length;

  return (
    <p>
      {completed} / {runs.length} complete
    </p>
  );
}

useRealtimeStream

Subscribe to a specific named stream from a run without subscribing to the run object itself.
"use client";
import { useRealtimeStream } from "@trigger.dev/react-hooks";

export function StreamViewer({ runId, accessToken }: { runId: string; accessToken: string }) {
  const { parts, error } = useRealtimeStream<string>(runId, "my-stream", {
    accessToken,
    onData: (chunk) => {
      console.log("New chunk:", chunk);
    },
  });

  if (error) return <p>Error: {error.message}</p>;

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

SWR vs Realtime hooks

The package ships two styles of hooks:
StyleBest for
SWR hooks (useRun)Fetch-once with optional polling. Good for displaying final run output.
Realtime hooks (useRealtimeRun, useRealtimeRunWithStreams, etc.)Live updates via SSE. Required for streaming AI output.
Prefer Realtime hooks over polling. SWR polling is limited by Trigger.dev API rate limits, while Realtime subscriptions use efficient server-sent events.

Next steps

Realtime overview

Learn how Trigger.dev Realtime works and what you can subscribe to.

Building with AI

Patterns for LLM streaming, agent loops, and human-in-the-loop.

Wait for token

Pause a task and resume it from the browser or an external service.

Build docs developers (and LLMs) love