Skip to main content
Version: 10.x

Inferring Types

It is often useful to wrap functionality of your @trpc/client or @trpc/react api within other functions. For this purpose, it's necessary to be able to infer input types and output types generated by your @trpc/server router.

Inference Helpers

@trpc/server exports the following helper types to assist with inferring these types from the AppRouter exported by your @trpc/server router:

  • inferProcedureOutput<TProcedure>
  • inferProcedureInput<TProcedure>

Let's assume we have this example router:

server.ts
ts
// @filename: server.ts
import { initTRPC } from '@trpc/server';
import { z } from "zod";
 
const t = initTRPC.create();
 
const appRouter = t.router({
post: t.router({
byId: t.procedure
.input(z.string())
.query(({ input }) => {
// imaginary db call
return { id: 1, title: 'tRPC is the best!' };
}),
create: t.procedure
.input(z.object({ title: z.string(), text: z.string(), }))
.mutation(({ input }) => {
// imaginary db call
return { id: 1, ...input };
}),
}),
});
 
export type AppRouter = typeof appRouter;
server.ts
ts
// @filename: server.ts
import { initTRPC } from '@trpc/server';
import { z } from "zod";
 
const t = initTRPC.create();
 
const appRouter = t.router({
post: t.router({
byId: t.procedure
.input(z.string())
.query(({ input }) => {
// imaginary db call
return { id: 1, title: 'tRPC is the best!' };
}),
create: t.procedure
.input(z.object({ title: z.string(), text: z.string(), }))
.mutation(({ input }) => {
// imaginary db call
return { id: 1, ...input };
}),
}),
});
 
export type AppRouter = typeof appRouter;

By traversing the router object, you can infer the types of the procedures. The following example shows how to infer the types of the procedures using the example appRouter:

client.ts
ts
// @filename: client.ts
import type { inferProcedureInput, inferProcedureOutput } from '@trpc/server';
import type { AppRouter } from './server';
 
type PostCreateInput = inferProcedureOutput<AppRouter['post']['create']>;
type PostCreateInput = { title: string; text: string; id: number; }
type PostCreateOutput = inferProcedureInput<AppRouter['post']['create']>;
type PostCreateOutput = { title: string; text: string; }
client.ts
ts
// @filename: client.ts
import type { inferProcedureInput, inferProcedureOutput } from '@trpc/server';
import type { AppRouter } from './server';
 
type PostCreateInput = inferProcedureOutput<AppRouter['post']['create']>;
type PostCreateInput = { title: string; text: string; id: number; }
type PostCreateOutput = inferProcedureInput<AppRouter['post']['create']>;
type PostCreateOutput = { title: string; text: string; }

Additional DX Helper Type

If you don't like the double-import from the above snippet, @trpc/server also exports a type GetInferenceHelpers<TRouter>. This lets you pass your router once at initialization, then import a single helper type when inferring types:

utils/trpc.ts
ts
import type { GetInferenceHelpers } from '@trpc/server';
import type { AppRouter } from './server';
 
export type InferProcedures = GetInferenceHelpers<AppRouter>;
utils/trpc.ts
ts
import type { GetInferenceHelpers } from '@trpc/server';
import type { AppRouter } from './server';
 
export type InferProcedures = GetInferenceHelpers<AppRouter>;
ts
export type AppRouterTypes = GetInferenceHelpers<AppRouter>;
 
type PostCreate = AppRouterTypes['post']['create'];
 
type PostCreateInput = PostCreate['input'];
type PostCreateInput = { title: string; text: string; }
type PostCreateOutput = PostCreate['output'];
type PostCreateOutput = { title: string; text: string; id: number; }
ts
export type AppRouterTypes = GetInferenceHelpers<AppRouter>;
 
type PostCreate = AppRouterTypes['post']['create'];
 
type PostCreateInput = PostCreate['input'];
type PostCreateInput = { title: string; text: string; }
type PostCreateOutput = PostCreate['output'];
type PostCreateOutput = { title: string; text: string; id: number; }

Infer TRPClientErrors based on your router

client.ts
ts
// @filename: client.ts
import { TRPCClientError } from '@trpc/client';
import type { AppRouter } from './server';
import { trpc } from './trpc';
 
export function isTRPCClientError(
cause: unknown,
): cause is TRPCClientError<AppRouter> {
return cause instanceof TRPCClientError;
}
 
async function main() {
try {
await trpc.post.byId.query('1');
} catch (cause) {
if (isTRPCClientError(cause)) {
// `cause` is now typed as your router's `TRPCClientError`
console.log('data', cause.data);
(property) TRPCClientError<Router<RouterDef<{}, DefaultErrorShape, {}, { post: Router<RouterDef<{}, DefaultErrorShape, {}, { byId: QueryProcedure<OverwriteKnown<{ _config: { ctx: {}; meta: {}; errorShape: DefaultErrorShape; transformer: DefaultDataTransformer; }; ... 6 more ...; _output_out: typeof unsetMarker; }, { ...; }>>; create: MutationProcedure<...>; }>> & { byId: QueryProcedure<OverwriteKnown<{ _config: { ctx: {}; meta: {}; errorShape: DefaultErrorShape; transformer: DefaultDataTransformer; }; ... 6 more ...; _output_out: typeof unsetMarker; }, { ...; }>>; create: MutationProcedure<...>; }; }>> & { post: Router<RouterDef<{}, DefaultErrorShape, {}, { byId: QueryProcedure<OverwriteKnown<{ _config: { ctx: {}; meta: {}; errorShape: DefaultErrorShape; transformer: DefaultDataTransformer; }; ... 6 more ...; _output_out: typeof unsetMarker; }, { ...; }>>; create: MutationProcedure<...>; }>> & { byId: QueryProcedure<OverwriteKnown<{ _config: { ctx: {}; meta: {}; errorShape: DefaultErrorShape; transformer: DefaultDataTransformer; }; ... 6 more ...; _output_out: typeof unsetMarker; }, { ...; }>>; create: MutationProcedure<...>; }; }>.data: Maybe<DefaultErrorData>
} else {
// [...]
}
}
}
 
main();
client.ts
ts
// @filename: client.ts
import { TRPCClientError } from '@trpc/client';
import type { AppRouter } from './server';
import { trpc } from './trpc';
 
export function isTRPCClientError(
cause: unknown,
): cause is TRPCClientError<AppRouter> {
return cause instanceof TRPCClientError;
}
 
async function main() {
try {
await trpc.post.byId.query('1');
} catch (cause) {
if (isTRPCClientError(cause)) {
// `cause` is now typed as your router's `TRPCClientError`
console.log('data', cause.data);
(property) TRPCClientError<Router<RouterDef<{}, DefaultErrorShape, {}, { post: Router<RouterDef<{}, DefaultErrorShape, {}, { byId: QueryProcedure<OverwriteKnown<{ _config: { ctx: {}; meta: {}; errorShape: DefaultErrorShape; transformer: DefaultDataTransformer; }; ... 6 more ...; _output_out: typeof unsetMarker; }, { ...; }>>; create: MutationProcedure<...>; }>> & { byId: QueryProcedure<OverwriteKnown<{ _config: { ctx: {}; meta: {}; errorShape: DefaultErrorShape; transformer: DefaultDataTransformer; }; ... 6 more ...; _output_out: typeof unsetMarker; }, { ...; }>>; create: MutationProcedure<...>; }; }>> & { post: Router<RouterDef<{}, DefaultErrorShape, {}, { byId: QueryProcedure<OverwriteKnown<{ _config: { ctx: {}; meta: {}; errorShape: DefaultErrorShape; transformer: DefaultDataTransformer; }; ... 6 more ...; _output_out: typeof unsetMarker; }, { ...; }>>; create: MutationProcedure<...>; }>> & { byId: QueryProcedure<OverwriteKnown<{ _config: { ctx: {}; meta: {}; errorShape: DefaultErrorShape; transformer: DefaultDataTransformer; }; ... 6 more ...; _output_out: typeof unsetMarker; }, { ...; }>>; create: MutationProcedure<...>; }; }>.data: Maybe<DefaultErrorData>
} else {
// [...]
}
}
}
 
main();