Skip to content
tRPC

Serializers

Plain JSON by default; danSON or superjson-style serializers keep Dates, Maps and files, and client types follow.

On this page

The serializer turns inputs and outputs into JSON on the wire. The default is plain JSON. To keep more types, pass the same serializer to the handler and to every link:

SerializerKeeps
jsonSerializer, the defaultplain JSON
createSerializer(), danSONDate, Map, Set, bigint, undefined, URL, files, …
createSyncSerializer(superjson)what the transformer knows (superjson, devalue, your own)

Every serializer streams deferred values: a procedure can return promises, generators and ReadableStreams at any depth, and each value goes through the serializer as it settles.

Choosing one

The serializer lives on the handler and the links, not on initTrpc:

import { function createSerializer(opts?: SerializerOptions): SerializercreateSerializer } from '@trpc12/trpc/serializer';

export const const serializer: Serializer<DansonForm>serializer = function createSerializer(opts?: SerializerOptions): SerializercreateSerializer();

createFetchHandler<{
    health: BuildProcedure<RootDef<{}, {}>, "query", {
        output: string;
        errors: never;
        services: never;
        unmapped: never;
    }>;
}, undefined>(opts: FetchHandlerOptions<{
    health: BuildProcedure<RootDef<{}, {}>, "query", {
        output: string;
        errors: never;
        services: never;
        unmapped: never;
    }>;
}, undefined>): FetchHandler<undefined>
createFetchHandler
({
BaseHandlerOptions<{ health: BuildProcedure<RootDef<{}, {}>, "query", { output: string; errors: never; services: never; unmapped: never; }>; }>.router: {
    health: BuildProcedure<RootDef<{}, {}>, "query", {
        output: string;
        errors: never;
        services: never;
        unmapped: never;
    }>;
}
router
, SerializerOption.serializer?: AnySerializer | undefinedserializer });
const
const client: TrpcClient<{
    health: BuildProcedure<RootDef<{}, {}>, "query", {
        output: string;
        errors: never;
        services: never;
        unmapped: never;
    }>;
}, {}, undefined, LinksTypes<readonly [TrpcLink<{
    form: DansonForm;
    provides: "response";
}, any>]>>
client
=
createTrpcClient<RouterType<{
    health: BuildProcedure<RootDef<{}, {}>, "query", {
        output: string;
        errors: never;
        services: never;
        unmapped: never;
    }>;
}>, readonly [TrpcLink<{
    form: DansonForm;
    provides: "response";
}, any>]>(opts: TypedClientOptions<RouterType<{
    health: BuildProcedure<RootDef<{}, {}>, "query", {
        output: string;
        errors: never;
        services: never;
        unmapped: never;
    }>;
}>, readonly [TrpcLink<{
    form: DansonForm;
    provides: "response";
}, any>]>): TrpcClient<...>
createTrpcClient
({
TypedClientOptions<RouterType<{ health: BuildProcedure<RootDef<{}, {}>, "query", { output: string; errors: never; services: never; unmapped: never; }>; }>, readonly [...]>.router: RouterType<{
    health: BuildProcedure<RootDef<{}, {}>, "query", {
        output: string;
        errors: never;
        services: never;
        unmapped: never;
    }>;
}>
router
:
routerType<{
    health: BuildProcedure<RootDef<{}, {}>, "query", {
        output: string;
        errors: never;
        services: never;
        unmapped: never;
    }>;
}>(): RouterType<{
    health: BuildProcedure<RootDef<{}, {}>, "query", {
        output: string;
        errors: never;
        services: never;
        unmapped: never;
    }>;
}>
routerType
<
type AppRouter = {
    health: BuildProcedure<RootDef<{}, {}>, "query", {
        output: string;
        errors: never;
        services: never;
        unmapped: never;
    }>;
}
AppRouter
>(),
TypedClientOptions<RouterType<{ health: BuildProcedure<RootDef<{}, {}>, "query", { output: string; errors: never; services: never; unmapped: never; }>; }>, readonly [...]>.links: readonly [TrpcLink<{
    form: DansonForm;
    provides: "response";
}, any>]
links
: [
httpLink<{
    url: string;
    serializer: Serializer<DansonForm>;
}>(opts: {
    url: string;
    serializer: Serializer<DansonForm>;
}): TrpcLink<{
    form: DansonForm;
    provides: "response";
}, any>
httpLink
({ url: stringurl, serializer: Serializer<DansonForm>serializer })],
});

wsLink, experimental_messagePortLink, experimental_socketLink and localLink take serializer the same way. With superjson or devalue, list the types the transformer rebuilds:

export const const serializer: Serializer<SyncForm<bigint | Date | Map<any, any> | Set<any> | undefined>>serializer = createSyncSerializer<bigint | Date | Map<any, any> | Set<any> | undefined>(transformer: SyncTransformer, opts?: SyncSerializerOptions): Serializer<SyncForm<bigint | Date | Map<any, any> | Set<any> | undefined>>createSyncSerializer<
  Date | interface Map<K, V>Map<any, any> | interface Set<T>Set<any> | bigint | undefined
>(class superjsonsuperjson);
// devalue: createSyncSerializer({ serialize: devalue.stringify, deserialize: devalue.parse })

If the two ends disagree, calls fail with UNSUPPORTED_PROTOCOL, with a message naming both serializers. Over HTTP the handler names its serializer in the trpc-serializer response header and httpLink checks it, after the server ran the call. Socket links (wsLink, experimental_messagePortLink, experimental_socketLink) name theirs in the connection's init message: the server answers with its own and runs none of that connection's calls on a mismatch.

The first link in the chain that has a serializer types the results. A type the serializer doesn't keep arrives in its JSON form, and the client's types say so:

import { function createTrpcClient<TSource, const TLinks extends ReadonlyArray<TrpcLink<any, RouterOf<TSource>>>>(opts: TypedClientOptions<TSource, TLinks> & DuplicateSwitchCheck<TLinks> & UnmetNeedCheck<TLinks>): TrpcClient<RouterOf<TSource>, LinksContext<TLinks>, undefined, LinksTypes<TLinks>>createTrpcClient, function httpLink<TOpts extends HttpLinkOptions>(opts: TOpts): TrpcLink<HttpLinkDecl<TOpts>>httpLink, function routerType<TRouter>(): RouterType<TRouter>routerType } from '@trpc12/trpc/client';
import { function createSerializer(opts?: SerializerOptions): SerializercreateSerializer } from '@trpc12/trpc/serializer';

const 
const router: {
    getPost: BuildProcedure<RootDef<Context, {}>, "query", {
        output: {
            at: Date;
            tags: Set<string>;
            draft: undefined;
        };
        errors: never;
        services: never;
        unmapped: never;
    }>;
}
router
= {
getPost: BuildProcedure<RootDef<Context, {}>, "query", {
    output: {
        at: Date;
        tags: Set<string>;
        draft: undefined;
    };
    errors: never;
    services: never;
    unmapped: never;
}>
getPost
: const t: TrpcRoot<Context, {}, RootDef<Context, {}>>t.TrpcRoot<Context, {}, RootDef<Context, {}>>.procedure: ProcedureBuilder<RootDef<Context, {}>>procedure.
ProcedureBuilder<RootDef<Context, {}>>.query<{
    at: Date;
    tags: Set<string>;
    draft: undefined;
}>(resolver: (opts: ResolverOpts<RootDef<Context, {}>>) => {
    at: Date;
    tags: Set<string>;
    draft: undefined;
}): BuildProcedure<RootDef<Context, {}>, "query", {
    output: {
        at: Date;
        tags: Set<string>;
        draft: undefined;
    };
    errors: never;
    services: never;
    unmapped: never;
}>
query
(() => ({
at: Dateat: new
var Date: DateConstructor
new () => Date (+3 overloads)
Date
(),
tags: Set<string>tags: new
var Set: SetConstructor
new <string>(iterable?: Iterable<string> | null | undefined) => Set<string> (+1 overload)
Set
(['a']),
draft: undefineddraft: var undefinedundefined, })), }; const
const json: TrpcClient<{
    getPost: BuildProcedure<RootDef<Context, {}>, "query", {
        output: {
            at: Date;
            tags: Set<string>;
            draft: undefined;
        };
        errors: never;
        services: never;
        unmapped: never;
    }>;
}, {}, undefined, LinksTypes<readonly [TrpcLink<{
    form: JsonForm;
    provides: "response";
}, any>]>>
json
=
createTrpcClient<RouterType<{
    getPost: BuildProcedure<RootDef<Context, {}>, "query", {
        output: {
            at: Date;
            tags: Set<string>;
            draft: undefined;
        };
        errors: never;
        services: never;
        unmapped: never;
    }>;
}>, readonly [TrpcLink<{
    form: JsonForm;
    provides: "response";
}, any>]>(opts: TypedClientOptions<RouterType<{
    getPost: BuildProcedure<RootDef<Context, {}>, "query", {
        output: {
            at: Date;
            tags: Set<string>;
            draft: undefined;
        };
        errors: never;
        services: never;
        unmapped: never;
    }>;
}>, readonly [...]>): TrpcClient<...>
createTrpcClient
({
TypedClientOptions<RouterType<{ getPost: BuildProcedure<RootDef<Context, {}>, "query", { output: { at: Date; tags: Set<string>; draft: undefined; }; errors: never; services: never; unmapped: never; }>; }>, readonly [...]>.router: RouterType<{
    getPost: BuildProcedure<RootDef<Context, {}>, "query", {
        output: {
            at: Date;
            tags: Set<string>;
            draft: undefined;
        };
        errors: never;
        services: never;
        unmapped: never;
    }>;
}>
router
:
routerType<{
    getPost: BuildProcedure<RootDef<Context, {}>, "query", {
        output: {
            at: Date;
            tags: Set<string>;
            draft: undefined;
        };
        errors: never;
        services: never;
        unmapped: never;
    }>;
}>(): RouterType<{
    getPost: BuildProcedure<RootDef<Context, {}>, "query", {
        output: {
            at: Date;
            tags: Set<string>;
            draft: undefined;
        };
        errors: never;
        services: never;
        unmapped: never;
    }>;
}>
routerType
<typeof
const router: {
    getPost: BuildProcedure<RootDef<Context, {}>, "query", {
        output: {
            at: Date;
            tags: Set<string>;
            draft: undefined;
        };
        errors: never;
        services: never;
        unmapped: never;
    }>;
}
router
>(),
TypedClientOptions<RouterType<{ getPost: BuildProcedure<RootDef<Context, {}>, "query", { output: { at: Date; tags: Set<string>; draft: undefined; }; errors: never; services: never; unmapped: never; }>; }>, readonly [...]>.links: readonly [TrpcLink<{
    form: JsonForm;
    provides: "response";
}, any>]
links
: [
httpLink<{
    url: string;
}>(opts: {
    url: string;
}): TrpcLink<{
    form: JsonForm;
    provides: "response";
}, any>
httpLink
({ url: stringurl: '/trpc' })],
}); const plain = await
const json: TrpcClient<{
    getPost: BuildProcedure<RootDef<Context, {}>, "query", {
        output: {
            at: Date;
            tags: Set<string>;
            draft: undefined;
        };
        errors: never;
        services: never;
        unmapped: never;
    }>;
}, {}, undefined, LinksTypes<readonly [TrpcLink<{
    form: JsonForm;
    provides: "response";
}, any>]>>
json
.
getPost: {
    query(input?: undefined, opts?: CallOptions<{}> | undefined): TrpcPromise<{
        at: string;
        tags: Record<string, never>;
    }, UnexpectedError>;
}
getPost
.
function query(input?: undefined, opts?: CallOptions<{}> | undefined): TrpcPromise<{
    at: string;
    tags: Record<string, never>;
}, UnexpectedError>
query
();
const plain: {
    at: string;
    tags: Record<string, never>;
}
const
const danson: TrpcClient<{
    getPost: BuildProcedure<RootDef<Context, {}>, "query", {
        output: {
            at: Date;
            tags: Set<string>;
            draft: undefined;
        };
        errors: never;
        services: never;
        unmapped: never;
    }>;
}, {}, undefined, LinksTypes<readonly [TrpcLink<{
    form: DansonForm;
    provides: "response";
}, any>]>>
danson
=
createTrpcClient<RouterType<{
    getPost: BuildProcedure<RootDef<Context, {}>, "query", {
        output: {
            at: Date;
            tags: Set<string>;
            draft: undefined;
        };
        errors: never;
        services: never;
        unmapped: never;
    }>;
}>, readonly [TrpcLink<{
    form: DansonForm;
    provides: "response";
}, any>]>(opts: TypedClientOptions<RouterType<{
    getPost: BuildProcedure<RootDef<Context, {}>, "query", {
        output: {
            at: Date;
            tags: Set<string>;
            draft: undefined;
        };
        errors: never;
        services: never;
        unmapped: never;
    }>;
}>, readonly [...]>): TrpcClient<...>
createTrpcClient
({
TypedClientOptions<RouterType<{ getPost: BuildProcedure<RootDef<Context, {}>, "query", { output: { at: Date; tags: Set<string>; draft: undefined; }; errors: never; services: never; unmapped: never; }>; }>, readonly [...]>.router: RouterType<{
    getPost: BuildProcedure<RootDef<Context, {}>, "query", {
        output: {
            at: Date;
            tags: Set<string>;
            draft: undefined;
        };
        errors: never;
        services: never;
        unmapped: never;
    }>;
}>
router
:
routerType<{
    getPost: BuildProcedure<RootDef<Context, {}>, "query", {
        output: {
            at: Date;
            tags: Set<string>;
            draft: undefined;
        };
        errors: never;
        services: never;
        unmapped: never;
    }>;
}>(): RouterType<{
    getPost: BuildProcedure<RootDef<Context, {}>, "query", {
        output: {
            at: Date;
            tags: Set<string>;
            draft: undefined;
        };
        errors: never;
        services: never;
        unmapped: never;
    }>;
}>
routerType
<typeof
const router: {
    getPost: BuildProcedure<RootDef<Context, {}>, "query", {
        output: {
            at: Date;
            tags: Set<string>;
            draft: undefined;
        };
        errors: never;
        services: never;
        unmapped: never;
    }>;
}
router
>(),
TypedClientOptions<RouterType<{ getPost: BuildProcedure<RootDef<Context, {}>, "query", { output: { at: Date; tags: Set<string>; draft: undefined; }; errors: never; services: never; unmapped: never; }>; }>, readonly [...]>.links: readonly [TrpcLink<{
    form: DansonForm;
    provides: "response";
}, any>]
links
: [
httpLink<{
    url: string;
    serializer: Serializer<DansonForm>;
}>(opts: {
    url: string;
    serializer: Serializer<DansonForm>;
}): TrpcLink<{
    form: DansonForm;
    provides: "response";
}, any>
httpLink
({ url: stringurl: '/trpc', serializer: Serializer<DansonForm>serializer: function createSerializer(opts?: SerializerOptions): SerializercreateSerializer() })],
}); const rich = await
const danson: TrpcClient<{
    getPost: BuildProcedure<RootDef<Context, {}>, "query", {
        output: {
            at: Date;
            tags: Set<string>;
            draft: undefined;
        };
        errors: never;
        services: never;
        unmapped: never;
    }>;
}, {}, undefined, LinksTypes<readonly [TrpcLink<{
    form: DansonForm;
    provides: "response";
}, any>]>>
danson
.
getPost: {
    query(input?: undefined, opts?: CallOptions<{}> | undefined): TrpcPromise<{
        at: Date;
        tags: Set<string>;
        draft: undefined;
    }, UnexpectedError>;
}
getPost
.
function query(input?: undefined, opts?: CallOptions<{}> | undefined): TrpcPromise<{
    at: Date;
    tags: Set<string>;
    draft: undefined;
}, UnexpectedError>
query
();
const rich: {
    at: Date;
    tags: Set<string>;
    draft: undefined;
}

Date (and anything with toJSON) becomes what toJSON returns, Map, Set and other objects without own keys become {}, undefined keys are dropped and undefined array items become null. Types that are already JSON are kept as written, so recursive and branded JSON types survive. JsonOf<T, Keeps> is that transform, for your own types.

A typed error's data goes through the same serializer: new TrpcError({ code: 'EXPIRED', data: { at: new Date() } }) reaches a plain-JSON client as data: { at: string }.

In-process callers (createRouterClient, createTrpcRsc, localLink({ serializer: false })) pass values by reference, so they keep native types whatever the serializer.

To keep plain JSON on the wire and still get Dates from a contract's Effect Schemas, put validationLink({ contract, output: 'json' }) before httpLink: it decodes results with the schemas' JSON codecs and types them as the schemas' types (Validating on the client).

Files

A root File or Blob output of an unbatched HTTP call is the response body, with any serializer. On a batching chain, plain JSON can't batch it, so such calls take { batch: false } (a type error otherwise). Files anywhere else, and files in inputs, need danSON; the other serializers fail before sending.

Query URLs

Query inputs go in the URL as ?input=<JSON>, with object keys sorted (array order is kept), so equal inputs make the same URL and hit the same HTTP cache entry. A serializer that makes refs (danSON's $n markers) adds &refs=. Long inputs fall back to POST (Links).

Custom types with danSON

const const decimal: CustomType<Decimal, string>decimal: interface CustomType<T, TSerialized = unknown>CustomType<class DecimalDecimal, string> = {
  CustomType<Decimal, string>.is: (value: unknown) => value is Decimalis: (v: unknownv) => v: unknownv instanceof class DecimalDecimal,
  CustomType<Decimal, string>.serialize: (value: Decimal) => stringserialize: (v: Decimalv) => v: Decimalv.Object.toString(): stringtoString(),
  CustomType<Decimal, string>.deserialize: (value: string) => Decimaldeserialize: (s: strings) => new constructor Decimal(value: string): DecimalDecimal(s: strings),
};
export const const serializer: Serializer<DansonForm>serializer = function createSerializer(opts?: SerializerOptions): SerializercreateSerializer({ SerializerOptions.types?: Record<string, CustomType<any, any>> | undefinedtypes: { type Decimal: CustomType<Decimal, string>Decimal: const decimal: CustomType<Decimal, string>decimal } });

Hydration

hydrationOptions({ client }) serializes TanStack Query data with the client's serializer (the first link that has one), so server-rendered data hydrates with the same types the client sees. Type the query context from the client, and it takes the links' form too:

const 
const makeClient: () => TrpcClient<{
    post: {
        byId: BuildProcedure<InputDef<RootDef<Context, {}>, ZodObject<{
            id: ZodString;
        }, $strip>>, "query", {
            output: Post;
            errors: TrpcError<"NOT_FOUND", {
                id: string;
            }>;
            services: never;
            unmapped: never;
        }>;
        list: BuildProcedure<RootDef<Context, {}>, "query", {
            output: Post[];
            errors: never;
            services: never;
            unmapped: never;
        }>;
        create: BuildProcedure<InputDef<With<RootDef<Context, {}>, {
            ctx: Context;
            ctxAdded: {
                ...;
            };
            inputOut: Unset;
            errors: TrpcError<...>;
            shorts: never;
        }>, ZodObject<...>>, "mutation", {
            ...;
        }>;
    };
}, {}, undefined, LinksTypes<...>>
makeClient
= () =>
createTrpcClient<RouterType<{
    post: {
        byId: BuildProcedure<InputDef<RootDef<Context, {}>, ZodObject<{
            id: ZodString;
        }, $strip>>, "query", {
            output: Post;
            errors: TrpcError<"NOT_FOUND", {
                id: string;
            }>;
            services: never;
            unmapped: never;
        }>;
        list: BuildProcedure<RootDef<Context, {}>, "query", {
            output: Post[];
            errors: never;
            services: never;
            unmapped: never;
        }>;
        create: BuildProcedure<InputDef<With<RootDef<Context, {}>, {
            ctx: Context;
            ctxAdded: {
                ...;
            };
            inputOut: Unset;
            errors: TrpcError<...>;
            shorts: never;
        }>, ZodObject<...>>, "mutation", {
            ...;
        }>;
    };
}>, readonly [...]>(opts: TypedClientOptions<...>): TrpcClient<...>
createTrpcClient
({
TypedClientOptions<RouterType<{ post: { byId: BuildProcedure<InputDef<RootDef<Context, {}>, ZodObject<{ id: ZodString; }, $strip>>, "query", { output: Post; errors: TrpcError<...>; services: never; unmapped: never; }>; list: BuildProcedure<...>; create: BuildProcedure<...>; }; }>, readonly [...]>.router: RouterType<{
    post: {
        byId: BuildProcedure<InputDef<RootDef<Context, {}>, ZodObject<{
            id: ZodString;
        }, $strip>>, "query", {
            output: Post;
            errors: TrpcError<"NOT_FOUND", {
                id: string;
            }>;
            services: never;
            unmapped: never;
        }>;
        list: BuildProcedure<RootDef<Context, {}>, "query", {
            output: Post[];
            errors: never;
            services: never;
            unmapped: never;
        }>;
        create: BuildProcedure<InputDef<With<RootDef<Context, {}>, {
            ctx: Context;
            ctxAdded: {
                ...;
            };
            inputOut: Unset;
            errors: TrpcError<...>;
            shorts: never;
        }>, ZodObject<...>>, "mutation", {
            ...;
        }>;
    };
}>
router
:
routerType<{
    post: {
        byId: BuildProcedure<InputDef<RootDef<Context, {}>, ZodObject<{
            id: ZodString;
        }, $strip>>, "query", {
            output: Post;
            errors: TrpcError<"NOT_FOUND", {
                id: string;
            }>;
            services: never;
            unmapped: never;
        }>;
        list: BuildProcedure<RootDef<Context, {}>, "query", {
            output: Post[];
            errors: never;
            services: never;
            unmapped: never;
        }>;
        create: BuildProcedure<InputDef<With<RootDef<Context, {}>, {
            ctx: Context;
            ctxAdded: {
                ...;
            };
            inputOut: Unset;
            errors: TrpcError<...>;
            shorts: never;
        }>, ZodObject<...>>, "mutation", {
            ...;
        }>;
    };
}>(): RouterType<...>
routerType
<
type AppRouter = {
    post: {
        byId: BuildProcedure<InputDef<RootDef<Context, {}>, ZodObject<{
            id: ZodString;
        }, $strip>>, "query", {
            output: Post;
            errors: TrpcError<"NOT_FOUND", {
                id: string;
            }>;
            services: never;
            unmapped: never;
        }>;
        list: BuildProcedure<RootDef<Context, {}>, "query", {
            output: Post[];
            errors: never;
            services: never;
            unmapped: never;
        }>;
        create: BuildProcedure<InputDef<With<RootDef<Context, {}>, {
            ctx: Context;
            ctxAdded: {
                ...;
            };
            inputOut: Unset;
            errors: TrpcError<...>;
            shorts: never;
        }>, ZodObject<...>>, "mutation", {
            ...;
        }>;
    };
}
AppRouter
>(),
TypedClientOptions<RouterType<{ post: { byId: BuildProcedure<InputDef<RootDef<Context, {}>, ZodObject<{ id: ZodString; }, $strip>>, "query", { output: Post; errors: TrpcError<...>; services: never; unmapped: never; }>; list: BuildProcedure<...>; create: BuildProcedure<...>; }; }>, readonly [...]>.links: readonly [TrpcLink<{
    form: DansonForm;
    provides: "response";
}, any>]
links
: [
httpLink<{
    url: string;
    serializer: Serializer<DansonForm>;
}>(opts: {
    url: string;
    serializer: Serializer<DansonForm>;
}): TrpcLink<{
    form: DansonForm;
    provides: "response";
}, any>
httpLink
({ url: stringurl, serializer: Serializer<DansonForm>serializer })],
}); export const [
const TrpcProvider: (props: TrpcProviderProps<{
    post: {
        byId: BuildProcedure<InputDef<RootDef<Context, {}>, ZodObject<{
            id: ZodString;
        }, $strip>>, "query", {
            output: Post;
            errors: TrpcError<"NOT_FOUND", {
                id: string;
            }>;
            services: never;
            unmapped: never;
        }>;
        list: BuildProcedure<RootDef<Context, {}>, "query", {
            output: Post[];
            errors: never;
            services: never;
            unmapped: never;
        }>;
        create: BuildProcedure<InputDef<With<RootDef<Context, {}>, {
            ctx: Context;
            ctxAdded: {
                ...;
            };
            inputOut: Unset;
            errors: TrpcError<...>;
            shorts: never;
        }>, ZodObject<...>>, "mutation", {
            ...;
        }>;
    };
}, {}, LinksTypes<...>>) => React.ReactNode
TrpcProvider
,
const useTrpc: () => TrpcQueryUtilsFor<{
    post: {
        byId: BuildProcedure<InputDef<RootDef<Context, {}>, ZodObject<{
            id: ZodString;
        }, $strip>>, "query", {
            output: Post;
            errors: TrpcError<"NOT_FOUND", {
                id: string;
            }>;
            services: never;
            unmapped: never;
        }>;
        list: BuildProcedure<RootDef<Context, {}>, "query", {
            output: Post[];
            errors: never;
            services: never;
            unmapped: never;
        }>;
        create: BuildProcedure<InputDef<With<RootDef<Context, {}>, {
            ctx: Context;
            ctxAdded: {
                ...;
            };
            inputOut: Unset;
            errors: TrpcError<...>;
            shorts: never;
        }>, ZodObject<...>>, "mutation", {
            ...;
        }>;
    };
}, {}, LinksTypes<...>>
useTrpc
] =
createTrpcContext<() => TrpcClient<{
    post: {
        byId: BuildProcedure<InputDef<RootDef<Context, {}>, ZodObject<{
            id: ZodString;
        }, $strip>>, "query", {
            output: Post;
            errors: TrpcError<"NOT_FOUND", {
                id: string;
            }>;
            services: never;
            unmapped: never;
        }>;
        list: BuildProcedure<RootDef<Context, {}>, "query", {
            output: Post[];
            errors: never;
            services: never;
            unmapped: never;
        }>;
        create: BuildProcedure<InputDef<With<RootDef<Context, {}>, {
            ctx: Context;
            ctxAdded: {
                ...;
            };
            inputOut: Unset;
            errors: TrpcError<...>;
            shorts: never;
        }>, ZodObject<...>>, "mutation", {
            ...;
        }>;
    };
}, {}, undefined, LinksTypes<...>>>(opts?: TrpcContextOptions): TrpcReactContext<...>
createTrpcContext
<typeof
const makeClient: () => TrpcClient<{
    post: {
        byId: BuildProcedure<InputDef<RootDef<Context, {}>, ZodObject<{
            id: ZodString;
        }, $strip>>, "query", {
            output: Post;
            errors: TrpcError<"NOT_FOUND", {
                id: string;
            }>;
            services: never;
            unmapped: never;
        }>;
        list: BuildProcedure<RootDef<Context, {}>, "query", {
            output: Post[];
            errors: never;
            services: never;
            unmapped: never;
        }>;
        create: BuildProcedure<InputDef<With<RootDef<Context, {}>, {
            ctx: Context;
            ctxAdded: {
                ...;
            };
            inputOut: Unset;
            errors: TrpcError<...>;
            shorts: never;
        }>, ZodObject<...>>, "mutation", {
            ...;
        }>;
    };
}, {}, undefined, LinksTypes<...>>
makeClient
>();

A router type alone (createTrpcContext<AppRouter>()) has no links, so it types the default JSON form.

See TanStack Query.