Skip to main content
Realtime Streams allow you to pipe streaming data from your Trigger.dev tasks to your frontend or backend applications in real-time. This is perfect for use cases like streaming AI completions, progress updates, or any continuous data flow.
Streams v2 requires SDK version 4.1.0 or later. Make sure to upgrade your @trigger.dev/sdk and @trigger.dev/react-hooks packages to use these features. If you’re on an earlier version, see the metadata.stream() documentation.

Overview

Streams v2 is a major upgrade that provides:
  • Unlimited stream length (previously capped at 2000 chunks)
  • Unlimited active streams per run (previously 5)
  • Improved reliability with automatic resumption on connection loss
  • 28-day stream retention (previously 1 day)
  • Multiple client streams can pipe to a single stream
  • Enhanced dashboard visibility for viewing stream data in real-time

Enabling Streams v2

Streams v2 is automatically enabled when triggering runs from the SDK using 4.1.0 or later. If you aren’t triggering via the SDK, you’ll need to explicitly enable v2 streams via setting the x-trigger-realtime-streams-version=v2 header when triggering the task. If you’d like to opt-out of the v2 streams, you can see so in one of the following two ways:

Option 1: Configure the SDK

Option 2: Environment Variable

Set the TRIGGER_V2_REALTIME_STREAMS=0 environment variable in your backend code (where you trigger tasks).

Limits Comparison

Quick Start

The recommended workflow for using Realtime Streams v2:
  1. Define your streams in a shared location using streams.define()
  2. Use the defined stream in your tasks with .pipe(), .append(), or .writer()
  3. Read from the stream using .read() or the useRealtimeStream hook in React
This approach gives you full type safety, better code organization, and easier maintenance as your application grows. The recommended way to work with streams is to define them once with streams.define(). This allows you to specify the chunk type and stream ID in one place, and then reuse that definition throughout your codebase with full type safety.

Creating a Defined Stream

Define your streams in a shared location (like app/streams.ts or trigger/streams.ts):
You can define streams for any JSON-serializable type:

Using Defined Streams in Tasks

Once defined, you can use all stream methods on your defined stream:

Reading from a Stream

Use the defined stream’s read() method to consume data from anywhere (frontend, backend, or another task):
With options:

Appending to a Stream

Use the defined stream’s append() method to add a single chunk:

Writing Multiple Chunks

Use the defined stream’s writer() method for more complex stream writing:

Using Defined Streams in React

Defined streams work seamlessly with the useRealtimeStream hook:

Direct Stream Methods (Without Defining)

We strongly recommend using streams.define() instead of direct methods. Defined streams provide better organization, full type safety, and make it easier to maintain your codebase as it grows.
If you have a specific reason to avoid defined streams, you can use stream methods directly by specifying the stream key each time.

Direct Piping

Direct Reading

Direct Appending

Direct Writing

Default Stream

Every run has a “default” stream, allowing you to skip the stream key entirely. This is useful for simple cases where you only need one stream per run. Using direct methods:

Targeting Different Runs

You can pipe streams to parent, root, or any other run using the target option. This works with both defined streams and direct methods.

With Defined Streams

With Direct Methods

Streaming from Outside a Task

If you specify a target run ID, you can pipe streams from anywhere (like a Next.js API route):

React Hook

Use the useRealtimeStream hook to subscribe to streams in your React components.

With Direct Stream Keys

If you prefer not to use defined streams, you can specify the stream key directly:

Using Default Stream

Hook Options

Complete Example: AI Streaming

Define the stream

Create the task

Frontend component

Migration from v1

If you’re using the old metadata.stream() API, here’s how to migrate to the recommended v2 approach:

Step 1: Define Your Streams

Create a shared streams definition file:

Step 2: Update Your Tasks

Replace metadata.stream() with the defined stream’s pipe() method:

Step 3: Update Your Frontend

Use the defined stream with useRealtimeStream:
If you prefer not to use defined streams, you can use direct methods:

Reliability Features

Streams v2 includes automatic reliability improvements:
  • Automatic resumption: If a connection is lost, both appending and reading will automatically resume from the last successful chunk
  • No data loss: Network issues won’t cause stream data to be lost
  • Idempotent operations: Duplicate chunks are automatically handled
These improvements happen automatically - no code changes needed.

Dashboard Integration

Streams are now visible in the Trigger.dev dashboard, allowing you to:
  • View stream data in real-time as it’s generated
  • Inspect historical stream data for completed runs
  • Debug streaming issues with full visibility into chunk delivery

Best Practices

  1. Always use streams.define(): Define your streams in a shared location for better organization, type safety, and code reusability. This is the recommended approach for all streams.
  2. Export stream types: Use InferStreamType to export types for your frontend components
  3. Handle errors gracefully: Always check for errors when reading streams in your UI
  4. Set appropriate timeouts: Adjust timeoutInSeconds based on your use case (AI completions may need longer timeouts)
  5. Target parent runs: When orchestrating with child tasks, pipe to parent runs for easier consumption
  6. Throttle frontend updates: Use throttleInMs in useRealtimeStream to prevent excessive re-renders
  7. Use descriptive stream IDs: Choose clear, descriptive IDs like "ai-output" or "progress" instead of generic names

Troubleshooting

Stream not appearing in dashboard

  • Ensure you’ve enabled Streams v2 via the future flag or environment variable
  • Verify your task is actually writing to the stream
  • Check that the stream key matches between writing and reading

Stream timeout errors

  • Increase timeoutInSeconds in your read() or useRealtimeStream() calls
  • Ensure your stream source is actively producing data
  • Check network connectivity between your application and Trigger.dev

Missing chunks

  • With v2, chunks should never be lost due to automatic resumption
  • Verify you’re reading from the correct stream key
  • Check the startIndex option if you’re not seeing expected chunks