tRPC SuperJSON: Preserve Dates, Maps, and BigInts Across the API Boundary
tRPC normally turns Dates into strings and drops undefined values when serializing JSON. Using a data transformer like superjson preserves rich JavaScript types, keeping your TypeScript types accurate from server to client. This post shows how to set it up, tests to verify, and the trade‑offs.
31 Mar 2026, 00:49 UTC

Why the Default tRPC JSON Serialization Breaks Your Types
When you declare a tRPC procedure that returns a Date, Map, or BigInt, the TypeScript compiler happily accepts it. At runtime, however, tRPC serializes the value to plain JSON. That means new Date() becomes a string, Map turns into an array, and BigInt is dropped entirely. The client receives an object that no longer matches the type signature, and downstream code that expects a real Date or Map will fail.
The SuperJSON Transformer: A Plug‑and‑Play Fix
tRPC lets you plug in a data transformer that runs on both the server and the client. The community‑standard choice is superjson, which adds a small metadata layer to the JSON payload. On the server, it serializes supported types into a format that the client can rehydrate back into the original JavaScript objects.
With superjson configured, a procedure that returns { createdAt: new Date() } will deliver an actual Date instance on the client, so calls to date-fns.format() or getTime() work out of the box.
Setting Up SuperJSON in a tRPC v11 Project
// server.ts
import { initTRPC } from "@trpc/server";
import superjson from "superjson";
const t = initTRPC.create({
transformer: superjson,
// other options like errorFormatter can go here
});
export const appRouter = t.router({
getTimestamp: t.procedure.query(() => ({
createdAt: new Date(),
count: BigInt(42),
options: new Map([['key', 'value']]),
})),
});
// client.ts
import { createTRPCReact } from "@trpc/react-query";
import superjson from "superjson";
import { httpBatchLink } from "@trpc/client/links/httpBatchLink";
export const trpc = createTRPCReact<AppRouter>();
export const trpcClient = trpc.createClient({
transformer: superjson,
links: [
httpBatchLink({
url: "/api/trpc",
}),
],
});
**Important**: The transformer must be identical on every entry point that talks to the API. If you use SSR pre‑fetch helpers, add the same transformer: superjson there as well.
Verifying the Transformation Works
Run your server and open the browser console on a page that calls
trpc.getTimestamp.query():trpc.getTimestamp.query().then(data => { console.log(data.createdAt instanceof Date); // true console.log(typeof data.count); // 'bigint' console.log(data.options instanceof Map); // true });Inspect the network tab. The response body will look like:
{ "json": {"createdAt": "2026-10-15T06:47:12.012Z", "count": "42", "options": [["key", "value"]] }, "meta": {"superjson": {"type": "v1", "data": {"createdAt": "Date", "count": "BigInt", "options": "Map"}}} }Try removing
transformer: superjsonfrom the client only. The call will throw aTRPCClientErrorwith a parse error because the client cannot interpret the metadata.
Trade‑Offs and Limitations
- Payload Size: SuperJSON adds a
metafield to every response. For small payloads the overhead is negligible, but for large data sets it can increase bandwidth by a few percent. - Debugging: The raw JSON is no longer human‑readable; you need to decode it or use a tool that understands superjson.
- Non‑tRPC Consumers: If a mobile app or a third‑party service calls the same endpoint without the transformer, it will receive the annotated payload and fail to parse.
- Supported Types: SuperJSON only preserves a fixed set of types (Date, BigInt, Map, Set, etc.). Custom class instances or functions are still lost.
- Version Mismatch: tRPC v10 and v11 use slightly different import paths for the transformer API. Verify your
package.jsonbefore copying snippets.
Next Steps for Your Project
1. Add superjson to package.json if you haven’t already.
2. Update all tRPC entry points (React Query client, SSR helpers, server‑side callers) to include transformer: superjson.
3. Run a quick smoke test: call a procedure that returns a Date and confirm the client receives a Date instance.
4. Monitor response sizes in your devtools and adjust your data model if the overhead becomes a concern.
5. Document the transformer usage in your API design guide so future contributors are aware of the requirement.
By following these steps, you’ll keep your TypeScript types accurate across the network and avoid the silent loss of rich JavaScript values that can trip up production code.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.