Skip to content

tRPC v12 isn’t on npm yet. The @trpc12/* packages on this site can’t be installed until it’s released. Use v11 today

tRPC

Hidden page. It waits for review: the sidebar, search and search engines leave it out, and listed pages don't link to it.

Validating on the client

experimental_validationLink checks inputs before sending and results when they arrive, against a contract.

On this page

Clients ship no schemas by default. To check calls against a contract in the browser, add experimental_validationLink: inputs are validated before anything is sent (the same BAD_REQUEST the server returns, so forms can show its issues), and results and subscription events are checked when they arrive (BAD_RESPONSE when the server drifted from the contract, with the issues on its cause):

const 
const client: TrpcClient<{
    post: {
        byId: ContractProcedure<"query", ContractWith<ContractWith<ContractRootDef<{}>, {
            inputIn: {
                id: string;
            };
            inputOut: {
                id: string;
            };
            errors: SchemaValidationError<z.ZodObject<{
                id: z.ZodString;
            }, z.core.$strip>>;
            requires: never;
        }>, {
            outputIn: {
                id: string;
                title: string;
                author: string | null;
                createdAt: Date;
            };
            outputOut: {
                id: string;
                title: string;
                author: string | null;
                createdAt: Date;
            };
            requires: never;
        }>>;
    };
}, {}, undefined, LinksTypes<readonly [...]>>
client
=
createTrpcClient<{
    post: {
        byId: ContractProcedure<"query", ContractWith<ContractWith<ContractRootDef<{}>, {
            inputIn: {
                id: string;
            };
            inputOut: {
                id: string;
            };
            errors: SchemaValidationError<z.ZodObject<{
                id: z.ZodString;
            }, z.core.$strip>>;
            requires: never;
        }>, {
            outputIn: {
                id: string;
                title: string;
                author: string | null;
                createdAt: Date;
            };
            outputOut: {
                id: string;
                title: string;
                author: string | null;
                createdAt: Date;
            };
            requires: never;
        }>>;
    };
}, readonly [TrpcLink<{}, any>, TrpcLink<...>]>(opts: TypedClientOptions<...>): TrpcClient<...>
createTrpcClient
({
TypedClientOptions<{ post: { byId: ContractProcedure<"query", ContractWith<ContractWith<ContractRootDef<{}>, { inputIn: { id: string; }; inputOut: { id: string; }; errors: SchemaValidationError<ZodObject<{ id: ZodString; }, $strip>>; requires: never; }>, { ...; }>>; }; }, readonly [...]>.router: {
    post: {
        byId: ContractProcedure<"query", ContractWith<ContractWith<ContractRootDef<{}>, {
            inputIn: {
                id: string;
            };
            inputOut: {
                id: string;
            };
            errors: SchemaValidationError<z.ZodObject<{
                id: z.ZodString;
            }, z.core.$strip>>;
            requires: never;
        }>, {
            outputIn: {
                id: string;
                title: string;
                author: string | null;
                createdAt: Date;
            };
            outputOut: {
                id: string;
                title: string;
                author: string | null;
                createdAt: Date;
            };
            requires: never;
        }>>;
    };
}
router
:
const appContract: {
    post: {
        byId: ContractProcedure<"query", ContractWith<ContractWith<ContractRootDef<{}>, {
            inputIn: {
                id: string;
            };
            inputOut: {
                id: string;
            };
            errors: SchemaValidationError<z.ZodObject<{
                id: z.ZodString;
            }, z.core.$strip>>;
            requires: never;
        }>, {
            outputIn: {
                id: string;
                title: string;
                author: string | null;
                createdAt: Date;
            };
            outputOut: {
                id: string;
                title: string;
                author: string | null;
                createdAt: Date;
            };
            requires: never;
        }>>;
    };
}
appContract
,
TypedClientOptions<{ post: { byId: ContractProcedure<"query", ContractWith<ContractWith<ContractRootDef<{}>, { inputIn: { id: string; }; inputOut: { id: string; }; errors: SchemaValidationError<ZodObject<{ id: ZodString; }, $strip>>; requires: never; }>, { ...; }>>; }; }, readonly [...]>.links: readonly [TrpcLink<{}, any>, TrpcLink<{
    form: JsonForm;
    provides: "response";
}, any>]
links
: [
function experimental_validationLink(opts: ValidationLinkOptions): TrpcLink (+1 overload)experimental_validationLink({ ValidationLinkOptions.contract: objectcontract:
const appContract: {
    post: {
        byId: ContractProcedure<"query", ContractWith<ContractWith<ContractRootDef<{}>, {
            inputIn: {
                id: string;
            };
            inputOut: {
                id: string;
            };
            errors: SchemaValidationError<z.ZodObject<{
                id: z.ZodString;
            }, z.core.$strip>>;
            requires: never;
        }>, {
            outputIn: {
                id: string;
                title: string;
                author: string | null;
                createdAt: Date;
            };
            outputOut: {
                id: string;
                title: string;
                author: string | null;
                createdAt: Date;
            };
            requires: never;
        }>>;
    };
}
appContract
}),
httpLink<{
    url: string;
}>(opts: {
    url: string;
}): TrpcLink<{
    form: JsonForm;
    provides: "response";
}, any>
httpLink
({ url: stringurl }),
], });

Results are checked against the output's type side, so Effect Schema transformations don't run a second time. With experimental_openApiLink, whose results are plain JSON, output: 'json' decodes them with the Effect Schema's JSON codec, so Dates, bigints and classes come back as their types. Standard Schema outputs are validated as they are.

See Contract-first for writing the contract, and Calling REST from the client for experimental_openApiLink.

Credits

experimental_validationLink follows oRPC's request validation and response validation plugins.