Configure SuperJSON transformer on both server initTRPC.create({ transformer: superjson }) and every client terminating link (httpBatchLink, httpLink, wsLink, httpSubscriptionLink) to support Date, Map, Set, BigInt over the wire. Transformer must match on both sides. In v11, transformer goes on individual links, not the client constructor.
npm install superjson
// server/trpc.ts
import { initTRPC } from '@trpc/server';
import superjson from 'superjson';
const t = initTRPC.create({
transformer: superjson,
});
export const router = t.router;
export const publicProcedure = t.procedure;
// client.ts
import { createTRPCClient, httpBatchLink } from '@trpc/client';
import superjson from 'superjson';
import type { AppRouter } from './server/trpc';
const client = createTRPCClient<AppRouter>({
links: [
httpBatchLink({
url: 'http://localhost:3000/trpc',
transformer: superjson,
}),
],
});
Now Date, Map, Set, BigInt, RegExp, undefined, and other non-JSON types survive the round trip.
import {
createTRPCClient,
httpBatchLink,
httpSubscriptionLink,
splitLink,
} from '@trpc/client';
import superjson from 'superjson';
import type { AppRouter } from './server/trpc';
const client = createTRPCClient<AppRouter>({
links: [
splitLink({
condition: (op) => op.type === 'subscription',
true: httpSubscriptionLink({
url: 'http://localhost:3000/trpc',
transformer: superjson,
}),
false: httpBatchLink({
url: 'http://localhost:3000/trpc',
transformer: superjson,
}),
}),
],
});
Every terminating link in every branch must have transformer: superjson.
import { createTRPCClient, createWSClient, wsLink } from '@trpc/client';
import superjson from 'superjson';
import type { AppRouter } from './server/trpc';
const wsClient = createWSClient({
url: 'ws://localhost:3000',
});
const client = createTRPCClient<AppRouter>({
links: [
wsLink<AppRouter>({
client: wsClient,
transformer: superjson,
}),
],
});
// server
import { z } from 'zod';
import { publicProcedure, router } from './trpc';
const appRouter = router({
getEvent: publicProcedure
.input(z.object({ id: z.string() }))
.query(({ input }) => {
return {
id: input.id,
name: 'Launch Party',
date: new Date('2025-01-01T00:00:00Z'),
};
}),
});
export type AppRouter = typeof appRouter;
// client
const event = await client.getEvent.query({ id: '1' });
console.log(event.date instanceof Date); // true
console.log(event.date.getFullYear()); // 2025
Without superjson, event.date would be a string like "2025-01-01T00:00:00.000Z".
Wrong:
// Server
const t = initTRPC.create({ transformer: superjson });
// Client
const client = createTRPCClient<AppRouter>({
links: [httpBatchLink({ url: 'http://localhost:3000/trpc' })],
});
Correct:
// Server
const t = initTRPC.create({ transformer: superjson });
// Client
const client = createTRPCClient<AppRouter>({
links: [
httpBatchLink({
url: 'http://localhost:3000/trpc',
transformer: superjson,
}),
],
});
Server encodes with superjson but client tries to parse raw JSON, causing "Unable to transform response" or garbled data.
Source: www/docs/server/data-transformers.md
The transformer option is on individual terminating links:
createTRPCClient<AppRouter>({
links: [
httpBatchLink({
url: 'http://localhost:3000/trpc',
transformer: superjson,
}),
],
});
In v11, transformer was moved from the client constructor to individual links. Passing it to createTRPCClient throws a TypeError.
Source: packages/client/src/internals/TRPCUntypedClient.ts
Wrong:
splitLink({
condition: (op) => op.type === 'subscription',
true: httpSubscriptionLink({
url: 'http://localhost:3000/trpc',
// missing transformer!
}),
false: httpBatchLink({
url: 'http://localhost:3000/trpc',
transformer: superjson,
}),
});
Correct:
splitLink({
condition: (op) => op.type === 'subscription',
true: httpSubscriptionLink({
url: 'http://localhost:3000/trpc',
transformer: superjson,
}),
false: httpBatchLink({
url: 'http://localhost:3000/trpc',
transformer: superjson,
}),
});
Every terminating link must have the same transformer. A missing transformer on one branch causes deserialization failures only for operations routed through that branch.
Source: www/docs/server/data-transformers.md
Wrong:
// Server -- no transformer
const t = initTRPC.create();
// Client
httpBatchLink({ url, transformer: superjson });
Correct:
// Server
const t = initTRPC.create({ transformer: superjson });
// Client
httpBatchLink({ url, transformer: superjson });
The transformer must be configured on both initTRPC.create() and every client link. Client-only transformer corrupts the request encoding because the server expects plain JSON.
Source: www/docs/server/data-transformers.md
client-setup -- create the tRPC client and configure linkslinks -- detailed options for each link type including transformerserver-setup -- initTRPC.create() where the server transformer is configured