superjson

Now Date, Map, Set, BigInt, RegExp, undefined, and other non-JSON types survive the round trip.

trpc/trpc56 installsMITSynced Aug 26

Works with

Claude CodeCursorCodex CLIGitHub CopilotGemini CLI

Agent Skills format with YAML frontmatter. Claude Code reads it as-is.

---
name: "superjson"
description: "Now Date, Map, Set, BigInt, RegExp, undefined, and other non-JSON types survive the round trip."
license: "MIT"
---

# tRPC -- SuperJSON Transformer

## Setup

### 1. Install superjson

```bash
npm install superjson
```

### 2. Add to initTRPC on the server

```ts
// 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;
```

### 3. Add to every terminating link on the client

```ts
// 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.

## Core Patterns

### SuperJSON with splitLink and Subscriptions

```ts
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`.

### SuperJSON with wsLink

```ts
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,
    }),
  ],
});
```

### Returning Dates from Procedures

```ts
// 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;
```

```ts
// 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"`.

## Common Mistakes

### [CRITICAL] Transformer on server but missing from client link

Wrong:

```ts
// Server
const t = initTRPC.create({ transformer: superjson });

// Client
const client = createTRPCClient<AppRouter>({
  links: [httpBatchLink({ url: 'http://localhost:3000/trpc' })],
});
```

Correct:

```ts
// 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

### [CRITICAL] Transformer goes on individual links, not createTRPCClient

The `transformer` option is on individual terminating links:

```ts
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

### [CRITICAL] Transformer on only some terminating links in splitLink

Wrong:

```ts
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:

```ts
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

### [HIGH] Using transformer on client but not on server

Wrong:

```ts
// Server -- no transformer
const t = initTRPC.create();

// Client
httpBatchLink({ url, transformer: superjson });
```

Correct:

```ts
// 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

## See Also

- `client-setup` -- create the tRPC client and configure links
- `links` -- detailed options for each link type including transformer
- `server-setup` -- initTRPC.create() where the server transformer is configured

More General & Other skills

← All General & Other skills

Check your AI visibility

One URL in, a 0–100 score and the exact fixes out.

RUN THE CHECK

Browse all the tools

15 tools across six categories
13 of them never send your data anywhere

Free · No signup · No trial clock

SEE THE DIRECTORY