Skip to main content
Version: 10.x

Inferring Types

It is often useful to wrap functionality of your @trpc/client or @trpc/react-query 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:

  • inferRouterInputs<TRouter>
  • inferRouterOutputs<TRouter>

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({
list: t.procedure
.query(() => {
// imaginary db call
return [{ id: 1, title: 'tRPC is the best!' }];
}),
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({
list: t.procedure
.query(() => {
// imaginary db call
return [{ id: 1, title: 'tRPC is the best!' }];
}),
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 { inferRouterInputs, inferRouterOutputs } from '@trpc/server';
import type { AppRouter } from './server';
 
type RouterInput = inferRouterInputs<AppRouter>;
type RouterOutput = inferRouterOutputs<AppRouter>;
 
type PostCreateInput = RouterInput['post']['create'];
type PostCreateInput = { title: string; text: string; }
type PostCreateOutput = RouterOutput['post']['create'];
type PostCreateOutput = { title: string; text: string; id: number; }
client.ts
ts
// @filename: client.ts
import type { inferRouterInputs, inferRouterOutputs } from '@trpc/server';
import type { AppRouter } from './server';
 
type RouterInput = inferRouterInputs<AppRouter>;
type RouterOutput = inferRouterOutputs<AppRouter>;
 
type PostCreateInput = RouterInput['post']['create'];
type PostCreateInput = { title: string; text: string; }
type PostCreateOutput = RouterOutput['post']['create'];
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<CreateRouterInner<RootConfig<{ ctx: {}; meta: {}; errorShape: DefaultErrorShape; transformer: DefaultDataTransformer; }>, { post: CreateRouterInner<RootConfig<{ ctx: {}; meta: {}; errorShape: DefaultErrorShape; transformer: DefaultDataTransformer; }>, { ...; }>; }>>.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<CreateRouterInner<RootConfig<{ ctx: {}; meta: {}; errorShape: DefaultErrorShape; transformer: DefaultDataTransformer; }>, { post: CreateRouterInner<RootConfig<{ ctx: {}; meta: {}; errorShape: DefaultErrorShape; transformer: DefaultDataTransformer; }>, { ...; }>; }>>.data: Maybe<DefaultErrorData>
} else {
// [...]
}
}
}
 
main();

Infer React Query options based on your router

When creating custom hooks around tRPC procedures, it's sometimes necesary to have the types of the options inferred from the router. You can do so via the inferReactQueryProcedureOptions helper exported from @trpc/react-query.

ts
// @filename: trpc.ts
import {
type inferReactQueryProcedureOptions,
createTRPCReact
} from '@trpc/react-query';
import type { inferRouterInputs } from '@trpc/server';
import type { AppRouter } from './server';
 
export type ReactQueryOptions = inferReactQueryProcedureOptions<AppRouter>;
export type RouterInputs = inferRouterInputs<AppRouter>;
 
export const trpc = createTRPCReact<AppRouter>();
 
// @filename: usePostCreate.ts
import { type ReactQueryOptions, trpc } from './trpc';
 
type PostCreateOptions = ReactQueryOptions['post']['create'];
 
function usePostCreate(options?: PostCreateOptions) {
const utils = trpc.useContext();
return trpc.post.create.useMutation({
...options,
onSuccess(post) {
// invalidate all queries on the post router
// when a new post is created
utils.post.invalidate();
options?.onSuccess?.(post);
},
});
}
 
// @filename: usePostById.ts
import { ReactQueryOptions, RouterInputs, trpc } from './trpc';
 
type PostByIdOptions = ReactQueryOptions['post']['byId'];
type PostByIdInput = RouterInputs['post']['byId'];
 
function usePostById(input: PostByIdInput, options?: PostByIdOptions) {
return trpc.post.byId.useQuery(input, options);
}
ts
// @filename: trpc.ts
import {
type inferReactQueryProcedureOptions,
createTRPCReact
} from '@trpc/react-query';
import type { inferRouterInputs } from '@trpc/server';
import type { AppRouter } from './server';
 
export type ReactQueryOptions = inferReactQueryProcedureOptions<AppRouter>;
export type RouterInputs = inferRouterInputs<AppRouter>;
 
export const trpc = createTRPCReact<AppRouter>();
 
// @filename: usePostCreate.ts
import { type ReactQueryOptions, trpc } from './trpc';
 
type PostCreateOptions = ReactQueryOptions['post']['create'];
 
function usePostCreate(options?: PostCreateOptions) {
const utils = trpc.useContext();
return trpc.post.create.useMutation({
...options,
onSuccess(post) {
// invalidate all queries on the post router
// when a new post is created
utils.post.invalidate();
options?.onSuccess?.(post);
},
});
}
 
// @filename: usePostById.ts
import { ReactQueryOptions, RouterInputs, trpc } from './trpc';
 
type PostByIdOptions = ReactQueryOptions['post']['byId'];
type PostByIdInput = RouterInputs['post']['byId'];
 
function usePostById(input: PostByIdInput, options?: PostByIdOptions) {
return trpc.post.byId.useQuery(input, options);
}