Validation Errors
oRPC provides built-in validation errors that work well by default. However, you might sometimes want to customize them.
Customizing with Client Interceptors
Client Interceptors are preferred because they run before error validation, ensuring that your custom errors are properly validated.
ts
import { onError, ORPCError, ValidationError } from '@orpc/server'
const handler = new RPCHandler(router, {
clientInterceptors: [
onError((error) => {
if (
error instanceof ORPCError
&& error.code === 'BAD_REQUEST'
&& error.cause instanceof ValidationError
) {
throw new ORPCError('INPUT_VALIDATION_FAILED', {
status: 422,
data: { issues: error.cause.issues },
cause: error.cause,
})
}
if (
error instanceof ORPCError
&& error.code === 'INTERNAL_SERVER_ERROR'
&& error.cause instanceof ValidationError
) {
throw new ORPCError('OUTPUT_VALIDATION_FAILED', {
cause: error.cause,
})
}
})
]
})
Customizing with Middleware
ts
import { onError, ORPCError, os, ValidationError } from '@orpc/server'
const base = os.use(onError((error) => {
if (
error instanceof ORPCError
&& error.code === 'BAD_REQUEST'
&& error.cause instanceof ValidationError
) {
throw new ORPCError('INPUT_VALIDATION_FAILED', {
status: 422,
data: { issues: error.cause.issues },
cause: error.cause,
})
}
if (
error instanceof ORPCError
&& error.code === 'INTERNAL_SERVER_ERROR'
&& error.cause instanceof ValidationError
) {
throw new ORPCError('OUTPUT_VALIDATION_FAILED', {
cause: error.cause,
})
}
}))
const getting = base
.input(z.object({ id: z.string().uuid() }))
.output(z.object({ id: z.string().uuid(), name: z.string() }))
.handler(async ({ input, context }) => {
return { id: input.id, name: 'name' }
})
Every procedure built from base
now uses these customized validation errors.
WARNING
Middleware applied before .input
/.output
catches validation errors by default, but this behavior can be configured.
Type‑Safe Validation Errors
As explained in the error handling guide, when you throw an ORPCError
instance, if the code
and data
match with the errors defined in the .errors
method, oRPC will treat it exactly as if you had thrown errors.[code]
using the type‑safe approach.
ts
import { onError, ORPCError, os, ValidationError } from '@orpc/server'
const base = os.errors({
INPUT_VALIDATION_FAILED: {
data: z.object({
issues: z.array(z.object({
message: z.string(),
})),
})
},
})
const ping = base.handler(() => 'ping')
const pong = base.handler(() => 'pong')
const handler = new RPCHandler({ ping, pong }, {
clientInterceptors: [
onError((error) => {
if (
error instanceof ORPCError
&& error.code === 'BAD_REQUEST'
&& error.cause instanceof ValidationError
) {
throw new ORPCError('INPUT_VALIDATION_FAILED', {
data: { issues: error.cause.issues },
cause: error.cause,
})
}
})
]
})