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:
Property Type Description submit(payload, options?) => voidTrigger the task with a typed payload isLoadingbooleantrue while the trigger request is in-flighthandleRunHandle | 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:
Property Type Description runRetrieveRunResult | undefinedThe fetched run object isLoadingbooleantrue on the first fetchisValidatingbooleantrue while re-fetching in the backgroundisErrorbooleantrue if an error occurrederrorError | 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:
Option Type Default Description accessTokenstringrequired Public Access Token enabledbooleantrueSet to false to pause the subscription stopOnCompletionbooleantrueStop the SSE connection when the run finishes onComplete(run, err?) => void— Called once when the run reaches a terminal state throttleInMsnumber16Minimum interval between state updates
Returns:
Property Type Description 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:
Property Type Description 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
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 >
);
}
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:
Style Best 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.