Skip to main content
Version: 10.x

Output Validation

tRPC gives you automatic type-safety of outputs without the need of adding a validator; however, it can be useful at times to strictly define the output type in order to prevent sensitive data of being leaked.

Similarily to input, an output validator can be added. The output validator is invoked with your payload.

When an output validator is defined, its inferred type is expected as the return type of your resolver (like t.procedure.query()).

info
  • This is entirely optional and only if you want to validate your output at runtime. This can be useful to ensure you do not accidentally leak any unexpected data.
  • If output validation fails, the server will respond with a INTERNAL_SERVER_ERROR.

Examples

tRPC works out-of-the-box with yup/superstruct/zod/myzod/custom validators/[..] - see test suite

With Zod

tsx
import { initTRPC } from '@trpc/server';
import { z } from 'zod';
export const t = initTRPC.create();
export const appRouter = t.router({
hello: t.procedure
.output(
z.object({
greeting: z.string(),
}),
)
// expects return type of { greeting: string }
.query(() => {
return {
greeting: 'hello',
};
}),
});
export type AppRouter = typeof appRouter;
tsx
import { initTRPC } from '@trpc/server';
import { z } from 'zod';
export const t = initTRPC.create();
export const appRouter = t.router({
hello: t.procedure
.output(
z.object({
greeting: z.string(),
}),
)
// expects return type of { greeting: string }
.query(() => {
return {
greeting: 'hello',
};
}),
});
export type AppRouter = typeof appRouter;

With Yup

tsx
import { initTRPC } from '@trpc/server';
import * as yup from 'yup';
export const t = initTRPC.create();
export const appRouter = t.router({
hello: t.procedure
.output(
yup.object({
greeting: yup.string().required(),
}),
)
.query(() => {
return {
greeting: 'hello',
};
}),
});
export type AppRouter = typeof appRouter;
tsx
import { initTRPC } from '@trpc/server';
import * as yup from 'yup';
export const t = initTRPC.create();
export const appRouter = t.router({
hello: t.procedure
.output(
yup.object({
greeting: yup.string().required(),
}),
)
.query(() => {
return {
greeting: 'hello',
};
}),
});
export type AppRouter = typeof appRouter;

With Superstruct

tsx
import { initTRPC } from '@trpc/server';
import { object, string } from 'superstruct';
export const t = initTRPC.create();
export const appRouter = t.router({
hello: t.procedure
.input(string())
.output(object({ greeting: string() }))
.query(({ input }) => {
return {
greeting: input,
};
}),
});
export type AppRouter = typeof appRouter;
tsx
import { initTRPC } from '@trpc/server';
import { object, string } from 'superstruct';
export const t = initTRPC.create();
export const appRouter = t.router({
hello: t.procedure
.input(string())
.output(object({ greeting: string() }))
.query(({ input }) => {
return {
greeting: input,
};
}),
});
export type AppRouter = typeof appRouter;

With custom validator

tsx
import { initTRPC } from '@trpc/server';
export const t = initTRPC.create();
export const appRouter = t.router({
hello: t.procedure
.output((value: any) => {
if (value && typeof value.greeting === 'string') {
return { greeting: value.greeting };
}
throw new Error('Greeting not found');
})
// expects return type of { greeting: string }
.query(() => {
return {
greeting: 'hello',
};
}),
});
export type AppRouter = typeof appRouter;
tsx
import { initTRPC } from '@trpc/server';
export const t = initTRPC.create();
export const appRouter = t.router({
hello: t.procedure
.output((value: any) => {
if (value && typeof value.greeting === 'string') {
return { greeting: value.greeting };
}
throw new Error('Greeting not found');
})
// expects return type of { greeting: string }
.query(() => {
return {
greeting: 'hello',
};
}),
});
export type AppRouter = typeof appRouter;