Skip to content
oRPC
Esc
navigateopen⌘Jpreview
On this page

Validation Customization

This guide explains how to customize validation in oRPC, including how to disable runtime validation and how to customize validation errors.

Disable Validation

You can disable runtime validation with the .$config.

const base = os.$config({
  /**
   * When enabled, input schemas are not validated at runtime.
   * Schemas are still used for type inference and OpenAPI generation.
   *
   * @warning Do not disable validation for schemas that transform values.
   *
   * @default false
   */
  disableInputValidation: true,

  /**
   * When enabled, output schemas are not validated at runtime.
   * Schemas are still used for type inference and OpenAPI generation.
   *
   * Useful when output schemas exist only for specification generation.
   *
   * @warning Do not disable validation for schemas that transform values.
   *
   * @default false
   */
  disableOutputValidation: true
})

Custom Validation Errors

You can catch validation errors with interceptors, client interceptors, or middleware applied before .input or .output and then throw a custom error. This is useful if you want to change the error message or shape.

import * as import zz from 'zod'
import { class ORPCError<TCode extends ORPCErrorCode, TData>
Typed error carrying a `code`, a `message`, and optional `data`. Throw it from handlers or middleware to produce typed error responses on the client.
@see{@link https://orpc.dev/docs/error-handling#orpcerror-class Error Handling - ORPCError Class}
ORPCError
, class ValidationError
Error thrown when input, output, or error data fails schema validation, carrying the standard-schema `issues` and the invalid data. Usually found as the `cause` of an `ORPCError`.
@see{@link https://orpc.dev/docs/advanced/validation-customization Validation Customization}
ValidationError
} from '@orpc/server'
const
const handler: RPCHandler<{
    headers?: IncomingHttpHeaders;
} & object>
handler
= new
new RPCHandler<{
    headers?: IncomingHttpHeaders;
} & object>(router: Router<{
    headers?: IncomingHttpHeaders;
} & object>, options?: NoInfer<RPCHandlerOptions<{
    headers?: IncomingHttpHeaders;
} & object>>): RPCHandler<{
    headers?: IncomingHttpHeaders;
} & object>
Serves an oRPC router over the RPC protocol using the Fetch API (Request/Response), supported by modern runtimes like Deno, Bun, Cloudflare Workers, and browsers.
@see{@link https://orpc.dev/docs/adapters/fetch-api Fetch API Adapter}
RPCHandler
(
const router: {
    planet: {
        list: ImplementedProcedure<{
            headers?: IncomingHttpHeaders;
        } & object, object, z.ZodObject<{
            limit: z.ZodOptional<z.ZodNumber>;
            cursor: z.ZodDefault<z.ZodNumber>;
        }, z.core.$strip>, z.ZodArray<z.ZodObject<{
            id: z.ZodNumber;
            name: z.ZodString;
            description: z.ZodOptional<z.ZodString>;
        }, z.core.$strip>>, object>;
        find: ImplementedProcedure<{
            headers?: IncomingHttpHeaders;
        } & object, object, z.ZodObject<{
            id: z.ZodNumber;
        }, z.core.$strip>, z.ZodObject<{
            id: z.ZodNumber;
            name: z.ZodString;
            description: z.ZodOptional<z.ZodString>;
        }, z.core.$strip>, object>;
        create: ImplementedProcedure<...>;
    };
}
router
, {
interceptors?: StandardHandlerInterceptor<{
    headers?: IncomingHttpHeaders;
} & object>[] | undefined
interceptor run after routing and before error handler, useful for error handling, logging, metrics, etc.
interceptors
: [
async ({
next: (options?: StandardHandlerInterceptorOptions<{
    headers?: IncomingHttpHeaders;
} & object> | undefined) => Promise<StandardResponse>
next
}) => {
try { return await
next: (options?: StandardHandlerInterceptorOptions<{
    headers?: IncomingHttpHeaders;
} & object> | undefined) => Promise<StandardResponse>
next
()
} catch (function (local var) error: unknownerror) { if ( function (local var) error: unknownerror instanceof class ORPCError<TCode extends ORPCErrorCode, TData>
Typed error carrying a `code`, a `message`, and optional `data`. Throw it from handlers or middleware to produce typed error responses on the client.
@see{@link https://orpc.dev/docs/error-handling#orpcerror-class Error Handling - ORPCError Class}
ORPCError
&& function (local var) error: ORPCError<any, any>error.ORPCError<any, any>.code: anycode === 'BAD_REQUEST' && function (local var) error: ORPCError<any, any>error.Error.cause?: unknowncause instanceof class ValidationError
Error thrown when input, output, or error data fails schema validation, carrying the standard-schema `issues` and the invalid data. Usually found as the `cause` of an `ORPCError`.
@see{@link https://orpc.dev/docs/advanced/validation-customization Validation Customization}
ValidationError
) { // If you only use Zod you can safely cast to ZodIssue[] const const zodError: z.ZodError<unknown>zodError = new import zz.
const ZodError: z.core.$constructor
new (def: z.core.$ZodIssue[]) => z.ZodError<unknown>
An Error-like class used to store Zod validation issues.
ZodError
(function (local var) error: ORPCError<any, any>error.Error.cause?: ValidationErrorcause.ValidationError.issues: readonly StandardSchemaV1.Issue[]
This array is readonly because the upstream Standard Schema returns readonly issues.
issues
as import zz.import corecore.type $ZodIssue = z.core.$ZodIssueUnrecognizedKeys | z.core.$ZodIssueInvalidType<unknown> | z.core.$ZodIssueTooBig<unknown> | z.core.$ZodIssueTooSmall<unknown> | z.core.$ZodIssueInvalidStringFormat | z.core.$ZodIssueNotMultipleOf<number | bigint> | z.core.$ZodIssueInvalidUnion | z.core.$ZodIssueInvalidKey<unknown> | z.core.$ZodIssueInvalidElement<unknown> | z.core.$ZodIssueInvalidValue<unknown> | z.core.$ZodIssueCustom$ZodIssue[])
throw new new ORPCError<"INPUT_VALIDATION_FAILED", _FlattenedError<unknown, string>>(code: "INPUT_VALIDATION_FAILED", options: ORPCErrorOptions<_FlattenedError<unknown, string>>): ORPCError<"INPUT_VALIDATION_FAILED", _FlattenedError<unknown, string>>
Typed error carrying a `code`, a `message`, and optional `data`. Throw it from handlers or middleware to produce typed error responses on the client.
@see{@link https://orpc.dev/docs/error-handling#orpcerror-class Error Handling - ORPCError Class}
ORPCError
('INPUT_VALIDATION_FAILED', {
message?: string | undefinedmessage: import zz.
function prettifyError(error: StandardSchemaV1<Input = unknown, Output = Input>.FailureResult): string
export prettifyError
prettifyError
(const zodError: z.ZodError<unknown>zodError),
data: _FlattenedError<unknown, string>data: import zz.
flattenError<unknown>(error: z.core.$ZodError<unknown>): _FlattenedError<unknown, string> (+1 overload)
export flattenError
flattenError
(const zodError: z.ZodError<unknown>zodError),
ErrorOptions.cause?: unknowncause: function (local var) error: ORPCError<any, any>error, }) } if ( function (local var) error: unknownerror instanceof class ORPCError<TCode extends ORPCErrorCode, TData>
Typed error carrying a `code`, a `message`, and optional `data`. Throw it from handlers or middleware to produce typed error responses on the client.
@see{@link https://orpc.dev/docs/error-handling#orpcerror-class Error Handling - ORPCError Class}
ORPCError
&& function (local var) error: ORPCError<any, any>error.ORPCError<any, any>.code: anycode === 'INTERNAL_SERVER_ERROR' && function (local var) error: ORPCError<any, any>error.Error.cause?: unknowncause instanceof class ValidationError
Error thrown when input, output, or error data fails schema validation, carrying the standard-schema `issues` and the invalid data. Usually found as the `cause` of an `ORPCError`.
@see{@link https://orpc.dev/docs/advanced/validation-customization Validation Customization}
ValidationError
) { // do not expose validation details for output validation errors throw new new ORPCError<"OUTPUT_VALIDATION_FAILED", unknown>(code: "OUTPUT_VALIDATION_FAILED", options?: ORPCErrorOptions<unknown> | undefined): ORPCError<"OUTPUT_VALIDATION_FAILED", unknown>
Typed error carrying a `code`, a `message`, and optional `data`. Throw it from handlers or middleware to produce typed error responses on the client.
@see{@link https://orpc.dev/docs/error-handling#orpcerror-class Error Handling - ORPCError Class}
ORPCError
('OUTPUT_VALIDATION_FAILED', {
ErrorOptions.cause?: unknowncause: function (local var) error: ORPCError<any, any>error, }) } throw function (local var) error: unknownerror } }, ], })

Typesafe Validation Errors

As explained in the error handling guide, if you throw an ORPCError whose code and data match an error defined with .errors, oRPC treats it the same as errors.[code].

This does not work in interceptors. Use client interceptors or middleware applied before .input or .output instead.

import { class ORPCError<TCode extends ORPCErrorCode, TData>
Typed error carrying a `code`, a `message`, and optional `data`. Throw it from handlers or middleware to produce typed error responses on the client.
@see{@link https://orpc.dev/docs/error-handling#orpcerror-class Error Handling - ORPCError Class}
ORPCError
, const os: Builder<DefaultInitialContext & object, Record<never, never>>
The oRPC procedure builder. Chain methods like `.input`, `.use`, and `.handler` to define procedures, then compose them into routers.
@see{@link https://orpc.dev/docs/procedure Procedure}
os
, class ValidationError
Error thrown when input, output, or error data fails schema validation, carrying the standard-schema `issues` and the invalid data. Usually found as the `cause` of an `ORPCError`.
@see{@link https://orpc.dev/docs/advanced/validation-customization Validation Customization}
ValidationError
} from '@orpc/server'
import * as import zz from 'zod' const
const base: Builder<DefaultInitialContext & object, {
    INPUT_VALIDATION_FAILED: {
        data: z.ZodObject<{
            formErrors: z.ZodArray<z.ZodString>;
            fieldErrors: z.ZodRecord<z.ZodString, z.ZodOptional<z.ZodArray<z.ZodString>>>;
        }, z.core.$strip>;
    };
}>
base
= const os: Builder<DefaultInitialContext & object, Record<never, never>>
The oRPC procedure builder. Chain methods like `.input`, `.use`, and `.handler` to define procedures, then compose them into routers.
@see{@link https://orpc.dev/docs/procedure Procedure}
os
.
Builder<DefaultInitialContext & object, Record<never, never>>.errors<{
    INPUT_VALIDATION_FAILED: {
        data: z.ZodObject<{
            formErrors: z.ZodArray<z.ZodString>;
            fieldErrors: z.ZodRecord<z.ZodString, z.ZodOptional<z.ZodArray<z.ZodString>>>;
        }, z.core.$strip>;
    };
}>(errors: {
    INPUT_VALIDATION_FAILED: {
        data: z.ZodObject<{
            formErrors: z.ZodArray<z.ZodString>;
            fieldErrors: z.ZodRecord<z.ZodString, z.ZodOptional<z.ZodArray<z.ZodString>>>;
        }, z.core.$strip>;
    };
}): Builder<DefaultInitialContext & object, {
    INPUT_VALIDATION_FAILED: {
        data: z.ZodObject<{
            formErrors: z.ZodArray<z.ZodString>;
            fieldErrors: z.ZodRecord<z.ZodString, z.ZodOptional<z.ZodArray<z.ZodString>>>;
        }, z.core.$strip>;
    };
}>
errors
({
type INPUT_VALIDATION_FAILED: {
    data: z.ZodObject<{
        formErrors: z.ZodArray<z.ZodString>;
        fieldErrors: z.ZodRecord<z.ZodString, z.ZodOptional<z.ZodArray<z.ZodString>>>;
    }, z.core.$strip>;
}
INPUT_VALIDATION_FAILED
: {
data: z.ZodObject<{
    formErrors: z.ZodArray<z.ZodString>;
    fieldErrors: z.ZodRecord<z.ZodString, z.ZodOptional<z.ZodArray<z.ZodString>>>;
}, z.core.$strip>
data
: import zz.
function object<{
    formErrors: z.ZodArray<z.ZodString>;
    fieldErrors: z.ZodRecord<z.ZodString, z.ZodOptional<z.ZodArray<z.ZodString>>>;
}>(shape?: {
    formErrors: z.ZodArray<z.ZodString>;
    fieldErrors: z.ZodRecord<z.ZodString, z.ZodOptional<z.ZodArray<z.ZodString>>>;
} | undefined, params?: string | {
    error?: string | z.core.$ZodErrorMap<NonNullable<z.core.$ZodIssueInvalidType<unknown> | z.core.$ZodIssueUnrecognizedKeys>> | undefined;
    message?: string | undefined | undefined;
} | undefined): z.ZodObject<{
    formErrors: z.ZodArray<...>;
    fieldErrors: z.ZodRecord<...>;
}, z.core.$strip>
object
({
formErrors: z.ZodArray<z.ZodString>formErrors: import zz.function array<z.ZodString>(element: z.ZodString, params?: string | z.core.$ZodArrayParams): z.ZodArray<z.ZodString>array(import zz.function string(params?: string | z.core.$ZodStringParams): z.ZodString (+1 overload)string()), fieldErrors: z.ZodRecord<z.ZodString, z.ZodOptional<z.ZodArray<z.ZodString>>>fieldErrors: import zz.function record<z.ZodString, z.ZodOptional<z.ZodArray<z.ZodString>>>(keyType: z.ZodString, valueType: z.ZodOptional<z.ZodArray<z.ZodString>>, params?: string | z.core.$ZodRecordParams): z.ZodRecord<z.ZodString, z.ZodOptional<z.ZodArray<z.ZodString>>>record(import zz.function string(params?: string | z.core.$ZodStringParams): z.ZodString (+1 overload)string(), import zz.function array<z.ZodString>(element: z.ZodString, params?: string | z.core.$ZodArrayParams): z.ZodArray<z.ZodString>array(import zz.function string(params?: string | z.core.$ZodStringParams): z.ZodString (+1 overload)string()).ZodType<any, any, $ZodArrayInternals<ZodString>>.optional(): z.ZodOptional<z.ZodArray<z.ZodString>>optional()), }), }, }) const
const example: DecoratedProcedure<DefaultInitialContext & object, object, z.ZodObject<{
    id: z.ZodUUID;
}, z.core.$strip>, Schema<void>, {
    INPUT_VALIDATION_FAILED: {
        data: z.ZodObject<{
            formErrors: z.ZodArray<z.ZodString>;
            fieldErrors: z.ZodRecord<z.ZodString, z.ZodOptional<z.ZodArray<z.ZodString>>>;
        }, z.core.$strip>;
    };
}, never>
example
=
const base: Builder<DefaultInitialContext & object, {
    INPUT_VALIDATION_FAILED: {
        data: z.ZodObject<{
            formErrors: z.ZodArray<z.ZodString>;
            fieldErrors: z.ZodRecord<z.ZodString, z.ZodOptional<z.ZodArray<z.ZodString>>>;
        }, z.core.$strip>;
    };
}>
base
.
Builder<DefaultInitialContext & object, { INPUT_VALIDATION_FAILED: { data: ZodObject<{ formErrors: ZodArray<ZodString>; fieldErrors: ZodRecord<ZodString, ZodOptional<...>>; }, $strip>; }; }>.input<z.ZodObject<{
    id: z.ZodUUID;
}, z.core.$strip>>(schema: z.ZodObject<{
    id: z.ZodUUID;
}, z.core.$strip>): BuilderWithInput<DefaultInitialContext & object, object, z.ZodObject<{
    id: z.ZodUUID;
}, z.core.$strip>, {
    INPUT_VALIDATION_FAILED: {
        data: z.ZodObject<{
            formErrors: z.ZodArray<z.ZodString>;
            fieldErrors: z.ZodRecord<z.ZodString, z.ZodOptional<z.ZodArray<z.ZodString>>>;
        }, z.core.$strip>;
    };
}>
input
(import zz.
function object<{
    id: z.ZodUUID;
}>(shape?: {
    id: z.ZodUUID;
} | undefined, params?: string | {
    error?: string | z.core.$ZodErrorMap<NonNullable<z.core.$ZodIssueInvalidType<unknown> | z.core.$ZodIssueUnrecognizedKeys>> | undefined;
    message?: string | undefined | undefined;
} | undefined): z.ZodObject<{
    id: z.ZodUUID;
}, z.core.$strip>
object
({ id: z.ZodUUIDid: import zz.function uuid(params?: string | z.core.$ZodUUIDParams): z.ZodUUIDuuid() }))
.
BuilderWithInput<DefaultInitialContext & object, object, ZodObject<{ id: ZodUUID; }, $strip>, { INPUT_VALIDATION_FAILED: { data: ZodObject<{ formErrors: ZodArray<ZodString>; fieldErrors: ZodRecord<ZodString, ZodOptional<...>>; }, $strip>; }; }>['handler']<void>(handler: ProcedureHandler<DefaultInitialContext & object, {
    id: string;
}, void, ORPCErrorConstructorMap<{
    INPUT_VALIDATION_FAILED: {
        data: z.ZodObject<{
            formErrors: z.ZodArray<z.ZodString>;
            fieldErrors: z.ZodRecord<z.ZodString, z.ZodOptional<z.ZodArray<z.ZodString>>>;
        }, z.core.$strip>;
    };
}>>): DecoratedProcedure<DefaultInitialContext & object, object, z.ZodObject<{
    id: z.ZodUUID;
}, z.core.$strip>, Schema<void>, {
    INPUT_VALIDATION_FAILED: {
        data: z.ZodObject<{
            formErrors: z.ZodArray<z.ZodString>;
            fieldErrors: z.ZodRecord<z.ZodString, z.ZodOptional<z.ZodArray<z.ZodString>>>;
        }, z.core.$strip>;
    };
}, never>
handler
(() => { /** do something */ })
const const handler: RPCHandler<DefaultInitialContext & object>handler = new new RPCHandler<DefaultInitialContext & object>(router: Router<DefaultInitialContext & object>, options?: NoInfer<RPCHandlerOptions<DefaultInitialContext & object>>): RPCHandler<DefaultInitialContext & object>
Serves an oRPC router over the RPC protocol using the Fetch API (Request/Response), supported by modern runtimes like Deno, Bun, Cloudflare Workers, and browsers.
@see{@link https://orpc.dev/docs/adapters/fetch-api Fetch API Adapter}
RPCHandler
({
example: DecoratedProcedure<DefaultInitialContext & object, object, z.ZodObject<{
    id: z.ZodUUID;
}, z.core.$strip>, Schema<void>, {
    INPUT_VALIDATION_FAILED: {
        data: z.ZodObject<{
            formErrors: z.ZodArray<z.ZodString>;
            fieldErrors: z.ZodRecord<z.ZodString, z.ZodOptional<z.ZodArray<z.ZodString>>>;
        }, z.core.$strip>;
    };
}, never>
example
}, {
clientInterceptors?: ProcedureClientInterceptor<DefaultInitialContext & object, Schema<unknown>, ErrorMap, any>[] | undefined
ClientInterceptor equivalent with createRouterClient.interceptors / createProcedure.interceptors useful for error handling, logging, metrics, etc. (not counting encoding/decoding)
clientInterceptors
: [
async ({ next: (options?: ProcedureClientInterceptorOptions<DefaultInitialContext & object, ErrorMap> | undefined) => PromiseWithError<unknown, any>next }) => { try { return await next: (options?: ProcedureClientInterceptorOptions<DefaultInitialContext & object, ErrorMap> | undefined) => PromiseWithError<unknown, any>next() } catch (function (local var) error: unknownerror) { if ( function (local var) error: unknownerror instanceof class ORPCError<TCode extends ORPCErrorCode, TData>
Typed error carrying a `code`, a `message`, and optional `data`. Throw it from handlers or middleware to produce typed error responses on the client.
@see{@link https://orpc.dev/docs/error-handling#orpcerror-class Error Handling - ORPCError Class}
ORPCError
&& function (local var) error: ORPCError<any, any>error.ORPCError<any, any>.code: anycode === 'BAD_REQUEST' && function (local var) error: ORPCError<any, any>error.Error.cause?: unknowncause instanceof class ValidationError
Error thrown when input, output, or error data fails schema validation, carrying the standard-schema `issues` and the invalid data. Usually found as the `cause` of an `ORPCError`.
@see{@link https://orpc.dev/docs/advanced/validation-customization Validation Customization}
ValidationError
) { // If you only use Zod you can safely cast to ZodIssue[] const const zodError: z.ZodError<unknown>zodError = new import zz.
const ZodError: z.core.$constructor
new (def: z.core.$ZodIssue[]) => z.ZodError<unknown>
An Error-like class used to store Zod validation issues.
ZodError
(function (local var) error: ORPCError<any, any>error.Error.cause?: ValidationErrorcause.ValidationError.issues: readonly StandardSchemaV1.Issue[]
This array is readonly because the upstream Standard Schema returns readonly issues.
issues
as import zz.import corecore.type $ZodIssue = z.core.$ZodIssueUnrecognizedKeys | z.core.$ZodIssueInvalidStringFormat | z.core.$ZodIssueInvalidType<unknown> | z.core.$ZodIssueTooBig<unknown> | z.core.$ZodIssueTooSmall<unknown> | z.core.$ZodIssueNotMultipleOf<number | bigint> | z.core.$ZodIssueInvalidUnion | z.core.$ZodIssueInvalidKey<unknown> | z.core.$ZodIssueInvalidElement<unknown> | z.core.$ZodIssueInvalidValue<unknown> | z.core.$ZodIssueCustom$ZodIssue[])
throw new new ORPCError<"INPUT_VALIDATION_FAILED", _FlattenedError<unknown, string>>(code: "INPUT_VALIDATION_FAILED", options: ORPCErrorOptions<_FlattenedError<unknown, string>>): ORPCError<"INPUT_VALIDATION_FAILED", _FlattenedError<unknown, string>>
Typed error carrying a `code`, a `message`, and optional `data`. Throw it from handlers or middleware to produce typed error responses on the client.
@see{@link https://orpc.dev/docs/error-handling#orpcerror-class Error Handling - ORPCError Class}
ORPCError
('INPUT_VALIDATION_FAILED', {
message?: string | undefinedmessage: import zz.
function prettifyError(error: StandardSchemaV1<Input = unknown, Output = Input>.FailureResult): string
export prettifyError
prettifyError
(const zodError: z.ZodError<unknown>zodError),
data: _FlattenedError<unknown, string>data: import zz.
flattenError<unknown>(error: z.core.$ZodError<unknown>): _FlattenedError<unknown, string> (+1 overload)
export flattenError
flattenError
(const zodError: z.ZodError<unknown>zodError),
ErrorOptions.cause?: unknowncause: function (local var) error: ORPCError<any, any>error, }) } throw function (local var) error: unknownerror } }, ], })

Last updated on August 6, 2026

Was this page helpful?