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 necessary 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);
}

You can also infer abstract types for router interfaces which you share around an application via a router factory. For example:

tsx
// @filename: factory.ts
 
import { t, publicProcedure } from './trpc';
 
// @trpc/react-query/shared exports several **Like types which can be used to generate abstract types
import { RouterLike, UtilsLike } from '@trpc/react-query/shared';
 
// Factory function written by you, however you need,
// so long as you can infer the resulting type of t.router() later
export function createMyRouter() {
return t.router({
createThing: publicProcedure
.input(ThingRequest)
.output(Thing)
.mutation(/* do work */),
listThings: publicProcedure
.input(ThingQuery)
.output(ThingArray)
.query(/* do work */),
})
}
 
// Infer the type of your router, and then generate the abstract types for use in the client
type MyRouterType = ReturnType<typeof createRouter>
export MyRouterLike = RouterLike<MyRouterType>
export MyRouterUtilsLike = UtilsLike<MyRouterType>
 
 
// @filename: server.ts
 
export type AppRouter = typeof appRouter
 
// Export your MyRouter types to the client
export type { MyRouterLike, MyRouterUtilsLike } from './factory'
 
 
// @filename: usePostCreate.tsx
import type { trpc, useContext, MyRouterLike, MyRouterUtilsLike } from './trpc';
 
type MyGenericComponentProps = {
route: MyRouterLike
utils: MyRouterUtilsLike
}
 
function MyGenericComponent({ route, utils }: MyGenericComponentProps) {
const thing = route.listThings.useQuery({
filter: "qwerty"
})
 
const mutation = route.doThing.useMutation({
onSuccess() {
utils.listThings.invalidate()
}
})
 
function handleClick() {
mutation.mutate({
name: "Thing 1"
})
}
 
return /* ui */
}
 
function MyPageComponent() {
const utils = useContext()
 
return (
<MyGenericComponent
route={trpc.deep.route.things}
utils={utils.deep.route.things}
/>
)
}
 
function MyOtherPageComponent() {
const utils = useContext()
 
return (
<MyGenericComponent
route={trpc.different.things}
utils={utils.different.things}
/>
)
}
tsx
// @filename: factory.ts
 
import { t, publicProcedure } from './trpc';
 
// @trpc/react-query/shared exports several **Like types which can be used to generate abstract types
import { RouterLike, UtilsLike } from '@trpc/react-query/shared';
 
// Factory function written by you, however you need,
// so long as you can infer the resulting type of t.router() later
export function createMyRouter() {
return t.router({
createThing: publicProcedure
.input(ThingRequest)
.output(Thing)
.mutation(/* do work */),
listThings: publicProcedure
.input(ThingQuery)
.output(ThingArray)
.query(/* do work */),
})
}
 
// Infer the type of your router, and then generate the abstract types for use in the client
type MyRouterType = ReturnType<typeof createRouter>
export MyRouterLike = RouterLike<MyRouterType>
export MyRouterUtilsLike = UtilsLike<MyRouterType>
 
 
// @filename: server.ts
 
export type AppRouter = typeof appRouter
 
// Export your MyRouter types to the client
export type { MyRouterLike, MyRouterUtilsLike } from './factory'
 
 
// @filename: usePostCreate.tsx
import type { trpc, useContext, MyRouterLike, MyRouterUtilsLike } from './trpc';
 
type MyGenericComponentProps = {
route: MyRouterLike
utils: MyRouterUtilsLike
}
 
function MyGenericComponent({ route, utils }: MyGenericComponentProps) {
const thing = route.listThings.useQuery({
filter: "qwerty"
})
 
const mutation = route.doThing.useMutation({
onSuccess() {
utils.listThings.invalidate()
}
})
 
function handleClick() {
mutation.mutate({
name: "Thing 1"
})
}
 
return /* ui */
}
 
function MyPageComponent() {
const utils = useContext()
 
return (
<MyGenericComponent
route={trpc.deep.route.things}
utils={utils.deep.route.things}
/>
)
}
 
function MyOtherPageComponent() {
const utils = useContext()
 
return (
<MyGenericComponent
route={trpc.different.things}
utils={utils.different.things}
/>
)
}

A more complete working example can be found here