useSubscription options interface
The UseTRPCSubscriptionOptions interface accepts the following options: onStarted (called when subscription is started), onData (called when new data is received), onError (called when an unrecoverable error occurs and subscription stops), onComplete (called when subscription is completed on server, transitioning to idle status with data undefined), and enabled (deprecated, use skipToken from @tanstack/react-query instead, will be removed in v12). Pass undefined for input if you need to set options but don't have input to pass.
useSubscription return type - status values
The useSubscription hook returns a discriminated union on status with four possible states: idle (subscription disabled or ended, data undefined, error null), connecting (trying to establish connection, may have previous reconnection error, data and error can be any value), pending (connected to server receiving data, data can be any value, error null), error (unrecoverable error occurred, subscription stopped, data can be any value, error is set).
useSubscription return object properties
Every useSubscription result object includes: status (one of 'idle', 'connecting', 'pending', or 'error'), data (TOutput or undefined), error (TError or null), and reset (a function to reset the subscription).
skipToken pauses subscription
If you pass skipToken from @tanstack/react-query to useSubscription, the subscription will be paused.
useSubscription hook overview
The useSubscription hook is used to subscribe to a subscription procedure on the server. It provides lifecycle callbacks and returns a discriminated union result based on subscription status.
useSubscription React example
Example React component using useSubscription: a PostFeed component maintains a posts array in local state, subscribes to onPostAdd with onData callback that appends new posts, displays connecting status, error status with reconnect button, and renders the posts list. The subscription is called with undefined as input and options object.
useSubscription example - PostFeed component code
```tsx
import { trpc } from '../utils/trpc';
type Post = { id: string; title: string };
export function PostFeed() {
const [posts, setPosts] = React.useState<Post[]>([]);
const subscription = trpc.onPostAdd.useSubscription(undefined, {
onData: (post) => {
setPosts((prev) => [...prev, post]);
},
});
return (
<div>
<h1>Live Feed</h1>
{subscription.status === 'connecting' && <p>Connecting...</p>}
{subscription.status === 'error' && (
<div>
<p>Error: {subscription.error.message}</p>
<button onClick={() => subscription.reset()}>Reconnect</button>
</div>
)}
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
</div>
);
}
```
Server subscription procedure example
```tsx
import EventEmitter, { on } from 'events';
import { initTRPC } from '@trpc/server';
export const t = initTRPC.create();
type Post = { id: string; title: string };
const ee = new EventEmitter();
export const appRouter = t.router({
onPostAdd: t.procedure.subscription(async function* (opts) {
for await (const [data] of on(ee, 'add', {
signal: opts.signal,
})) {
const post = data as Post;
yield post;
}
}),
});
export type AppRouter = typeof appRouter;
```
This example shows a subscription procedure that yields Post objects from an EventEmitter.
subscriptionOptions for type-safe subscription configuration
The subscriptionOptions method is available on all subscription procedures. It provides a type-safe identity function for constructing options that can be passed to useSubscription. Requires either httpSubscriptionLink or wsLink configured in the tRPC client. The options can include enabled, onStarted, onData, onError, and onConnectionStateChange callbacks.
useSubscription hook subscription status values
The useSubscription hook from @trpc/tanstack-react-query provides a status property that can be one of four values: 'idle' (subscription is disabled or ended), 'connecting' (trying to establish a connection), 'pending' (connected to the server, receiving data), or 'error' (an error occurred and the subscription is stopped).
useSubscription hook provides data, error, and reset
The useSubscription hook returns an object with data property (the lastly received data), error property (the lastly received error), status property (current subscription status), and reset() method to reset the subscription.
tRPC live query solution
trpc-live provides a live query solution for tRPC.
tRPC supports subscriptions
tRPC supports typesafe real-time updates to applications through subscriptions.
Server-sent events (SSE) subscriptions
tRPC v11 introduces support for server-sent events (SSE) for subscriptions using httpSubscriptionLink. This allows real-time updates without a WebSocket server. The client can automatically reconnect and resume if the connection is lost.
Stopping subscriptions from server
Subscriptions can now be stopped from the server by returning from an async generator function. When a subscription async generator returns, it stops the subscription on the client and triggers the onComplete callback.
Subscription output type changed to AsyncGenerator
In v11, subscription procedure output type has changed to AsyncGenerator. The inferred output type is now SubscriptionProcedure<{ input: __INPUT__; output: AsyncGenerator<__OUTPUT__, void, unknown> }> instead of the previous format without the wrapper. Use a helper like type inferAsyncIterableYield<TOutput> = TOutput extends AsyncGenerator<infer $Yield> ? $Yield : never to infer the yield value.
Output validators in subscriptions
tRPC v11 adds support for output validators in subscriptions, allowing validation of data yielded by subscription async generators.
HTTP subscription link ping and reconnect configuration
HTTP subscriptions can be configured with ping intervals and reconnect timeouts. On the server, configure sse: { ping: { enabled: true, intervalMs: 15_000 }, client: { reconnectAfterInactivityMs: 20_000 } } in initTRPC.create(). This keeps connections alive and allows automatic reconnection if no messages are received.
Deprecation of subscriptions returning Observables
Subscriptions returning Observables are deprecated in v11. Use async generator functions with httpSubscriptionLink instead.
Moved experimental.sseSubscriptions to sse
The experimental.sseSubscriptions option has been moved to sse in the initTRPC.create() configuration.
Enable WebSocket support in Fastify adapter
To enable WebSockets in the Fastify adapter, install @fastify/websocket (minimum version 3.11.0), register it with server.register(ws), add subscriptions to the router, and set useWSS: true in the fastifyTRPCPlugin options.
Fastify WebSocket keepAlive configuration
The Fastify adapter supports keepAlive options for WebSocket connections. Set keepAlive.enabled to true to enable heartbeat messages. Configuration: keepAlive.pingMs (milliseconds) - server ping message interval, default 30000; keepAlive.pongWaitMs (milliseconds) - connection is terminated if pong message is not received within this time, default 5000.
Fastify subscription example
Example of adding a subscription to a Fastify tRPC router:
export const appRouter = t.router({
randomNumber: t.procedure.subscription(async function* () {
while (true) {
yield { randomNumber: Math.random() };
await new Promise((resolve) => setTimeout(resolve, 1000));
}
}),
});
Fastify WebSocket server setup example
Example of setting up a Fastify server with WebSocket support for tRPC subscriptions:
import ws from '@fastify/websocket';
import {
fastifyTRPCPlugin,
FastifyTRPCPluginOptions,
} from '@trpc/server/adapters/fastify';
import fastify from 'fastify';
import { createContext } from './context';
import { appRouter, type AppRouter } from './router';
const server = fastify();
server.register(ws);
server.register(fastifyTRPCPlugin, {
useWSS: true,
trpcOptions: {
router: appRouter,
createContext,
keepAlive: {
enabled: true,
pingMs: 30000,
pongWaitMs: 5000,
},
},
});
Subscription with lastEventId tracking example
Example of a subscription procedure that tracks event IDs:
```ts
import { initTRPC, tracked } from '@trpc/server';
import { z } from 'zod';
const t = initTRPC.create();
const publicProcedure = t.procedure;
const router = t.router;
export const subRouter = router({
onPostAdd: publicProcedure
.input(
z
.object({
lastEventId: z.string().nullish(),
})
.optional(),
)
.subscription(async function* (opts) {
const iterable = ee.toIterable('add', {
signal: opts.signal,
});
if (opts.input?.lastEventId) {
// fetch posts since the last event id and yield them
}
for await (const [data] of iterable) {
const post = data as Post;
yield tracked(post.id, post);
}
}),
});
```
This shows how to accept lastEventId in input, fetch historical data if reconnecting, and yield tracked events.
Subscriptions overview
Subscriptions are a type of real-time event stream between the client and server. Use subscriptions when you need to push real-time updates to the client. With tRPC's subscriptions, the client establishes and maintains a persistent connection to the server and automatically attempts to reconnect and recover gracefully if disconnected.
WebSockets vs Server-sent Events for subscriptions
tRPC supports both WebSockets and Server-sent Events (SSE) for subscriptions. For WebSockets, see the WebSockets documentation. For SSE, see the httpSubscriptionLink documentation. SSE is recommended for subscriptions as it is easier to setup and does not require setting up a WebSocket server.
Basic subscription procedure with generator
A subscription procedure is defined using `.subscription(async function*() {})` with an async generator function. The function receives opts with a signal (AbortSignal) that will be aborted when the client disconnects. Events are sent to the client by yielding values from the generator.
tracked() helper for automatic reconnection
Use the `tracked()` helper when yielding events to include an event ID. The client will automatically reconnect when disconnected and send the last known ID via `lastEventId`. For SSE, this uses the EventSource spec and propagates through `lastEventId` in your `.input()`. For WebSockets, the wsLink automatically sends the last known ID and updates it as the browser receives data.
Polling subscription pattern
Use a `while (!opts.signal.aborted)` loop to periodically fetch data from a source like a database and push it to the client. Track the lastEventId (for example, using createdAt timestamp), fetch only new data since that ID, and yield tracked events. Add a sleep delay between polls to avoid hammering the database.
Polling subscription example with database
Example of a subscription that polls a database for new posts:
```ts
import { tracked } from '@trpc/server';
import { z } from 'zod';
export const subRouter = router({
onPostAdd: publicProcedure
.input(
z.object({
lastEventId: z.coerce.date().nullish(),
}),
)
.subscription(async function* (opts) {
let lastEventId = opts.input?.lastEventId ?? null;
while (!opts.signal!.aborted) {
const posts = await db.post.findMany({
where: lastEventId
? {
createdAt: {
gt: lastEventId,
},
}
: undefined,
orderBy: {
createdAt: 'asc',
},
});
for (const post of posts) {
yield tracked(post.createdAt.toJSON(), post);
lastEventId = post.createdAt;
}
await sleep(1_000);
}
}),
});
```
This example tracks the createdAt timestamp of posts and only fetches new posts since the last known timestamp.
Stopping a subscription from the server
To stop a subscription from the server, simply return from the generator function. On the client, call `.unsubscribe()` on the subscription to stop receiving events.
Stopping subscription server-side example
Example of stopping a subscription from the server:
```ts
export const subRouter = router({
onPostAdd: publicProcedure
.input(
z.object({
lastEventId: z.coerce.number().min(0).optional(),
}),
)
.subscription(async function* (opts) {
let index = opts.input.lastEventId ?? 0;
while (!opts.signal!.aborted) {
const idx = index++;
if (idx > 100) {
return;
}
await new Promise((resolve) => setTimeout(resolve, 10));
}
}),
});
```
When the subscription returns, the client will disconnect from the subscription.
Cleanup side effects in subscriptions with try...finally
Use the try...finally pattern to clean up side-effects of your subscription. tRPC invokes the .return() of the Generator Instance when the subscription stops for any reason, so the finally block will always execute.
Subscription cleanup example
Example of cleaning up side-effects in a subscription:
```ts
export const subRouter = router({
onPostAdd: publicProcedure.subscription(async function* (opts) {
let timeout: ReturnType<typeof setTimeout> | undefined;
try {
for await (const [data] of on(ee, 'add', {
signal: opts.signal,
})) {
timeout = setTimeout(() => console.log('Pretend like this is useful'));
const post = data as Post;
yield post;
}
} finally {
if (timeout) clearTimeout(timeout);
}
}),
});
```
The finally block ensures that the timeout is cleared when the subscription stops.
Setup event listener before fetching historical data in subscriptions
When fetching historical data based on lastEventId and capturing all events is critical, set up the event listener before fetching events from your database. This prevents newly emitted events from being ignored while yielding the original batch of historical data.
WebSocket RPC SubscriptionRequest interface
A subscription start request has: id (number or string), jsonrpc (optional, '2.0'), method ('subscription'), and params object containing path (string) and optional input (unknown, serialized by transformer).
WebSocket RPC SubscriptionResponse interface
A subscription response has: id (number or string), jsonrpc (optional, '2.0'), and result object. Result is either { type: 'data', data: TData } (subscription emitted data), { type: 'started' } (subscription started), or { type: 'stopped' } (subscription stopped).
Server reconnect notification
The server can send a message with { id: null, type: 'reconnect' } to tell clients to reconnect before shutdown. This is invoked by calling wssHandler.broadcastReconnectNotification().
AbortSignal usage in subscriptions
Pass opts.signal to event emitters or async iterators in subscriptions. The AbortSignal is automatically triggered when the subscription is aborted by the client, canceling the event listener.
Links for routing queries/mutations to HTTP and subscriptions over WebSockets
Use Links to route different RPC methods to different transports: send queries and mutations over HTTP while routing subscriptions over WebSockets.
WebSocket RPC SubscriptionStopRequest interface
A subscription stop request has: id (number or string, the id of the subscription to cancel), jsonrpc (optional, '2.0'), and method ('subscription.stop').
applyWSSHandler setup for WebSocket server
Use `applyWSSHandler` from '@trpc/server/adapters/ws' to configure a WebSocket server. Pass an object with: wss (the WebSocketServer instance), router (your appRouter), createContext (function creating context), and optionally keepAlive settings. keepAlive has properties enabled (boolean), pingMs (interval in milliseconds for server ping messages, default 30000), and pongWaitMs (milliseconds to wait for pong before terminating connection, default 5000).
WebSocket server with ws library setup
Create a WebSocket server with `new WebSocketServer({ port: 3001 })` from the 'ws' package. Listen for 'connection' events to track client connections. Call `handler.broadcastReconnectNotification()` on SIGTERM to notify clients before shutdown.
createWSClient configuration
Use `createWSClient` from '@trpc/client' to create a WebSocket client connection. Pass an object with: url (the WebSocket endpoint), and optionally connectionParams (an async function returning authentication data like tokens).
wsLink for TRPCClient configuration
Configure a TRPCClient to use WebSockets by passing wsLink in the links array. wsLink accepts an object with: client (the createWSClient instance), and optionally transformer (for serialization like superjson).
ConnectionParams authentication in WebSockets
Authenticate WebSocket connections by defining connectionParams in createWSClient. These params are sent as the first message when the client establishes a connection. On the server, access them via opts.info.connectionParams in the createContext function for CreateWSSContextFnOptions.
tracked() helper for subscription resumption
Use `tracked()` from '@trpc/server' to wrap subscription events with an id. When the client disconnects, it automatically reconnects and sends the lastEventId as input, allowing the server to resume from the last received event. The client can send an initial lastEventId during subscription setup.
Subscription input with lastEventId
Subscriptions can receive an optional input object containing lastEventId (string or null). On the first call, lastEventId is whatever was passed in initial setup. On reconnection, it is the last event id the client received. This enables resuming from a known point.
WebSocket RPC RequestMessage interface
A query or mutation request message has: id (number or string), jsonrpc (optional, '2.0'), method ('query' or 'mutation'), and params object containing path (string) and optional input (unknown, serialized by transformer).
WebSocket RPC ResponseMessage interface
A query or mutation response message has: id (number or string), jsonrpc (optional, '2.0'), and result object with type ('data', always 'data' for queries/mutations) and data (the output from the procedure).
ConnectionParamsMessage interface
When connection is initialized with ?connectionParams=1, the first message must be a ConnectionParamsMessage with: data (Record<string, string> or null) and method ('connectionParams').
Output validation of subscriptions
Subscriptions can use the same validation techniques as regular procedures since subscriptions are async iterators. Output validators can be applied to subscription responses.
subscriptionOptions for real-time data with useSubscription
Use trpc.procedure.subscriptionOptions(input, options?) with useSubscription hook from '@trpc/tanstack-react-query'. The options parameter accepts onData and onError callbacks. The subscription returns status and data properties. Example: useSubscription(trpc.chat.onMessage.subscriptionOptions({ channelId: 'general' }, { onData: (message) => {...}, onError: (err) => {...} })).
tRPC subscription setup
Set up real-time subscriptions using SSE or WebSocket. Refer to subscriptions skill for implementation details.
WebSocket and subscription support in Next.js starter
The next-prisma-websockets-starter example includes WebSocket and subscription support integrated with tRPC and Next.js