Skip to content
oRPC
Esc
navigateopen⌘Jpreview
On this page

Router Contract

Router contracts define the shape of a router without including any business logic. Use them for documentation, testing, and keeping multiple implementations of the same router aligned.

Overview

Define a router contract as a plain JavaScript object where each key maps to a procedure contract:

import { const oc: ContractBuilder<object>
The contract builder — the entry point for defining procedure and router contracts (input/output schemas, errors, and metadata) without any business logic.
@see{@link https://orpc.dev/docs/contract/procedure Procedure Contract}
oc
} from '@orpc/contract'
const const ping: ProcedureContractBuilderWithOutput<z.ZodString, object>ping = const oc: ContractBuilder<object>
The contract builder — the entry point for defining procedure and router contracts (input/output schemas, errors, and metadata) without any business logic.
@see{@link https://orpc.dev/docs/contract/procedure Procedure Contract}
oc
.ContractBuilder<object>.output<z.ZodString>(schema: z.ZodString): ProcedureContractBuilderWithOutput<z.ZodString, object>output(import zz.function string(params?: string | z.core.$ZodStringParams): z.ZodString (+1 overload)string())
const const pong: ProcedureContractBuilderWithOutput<z.ZodString, object>pong = const oc: ContractBuilder<object>
The contract builder — the entry point for defining procedure and router contracts (input/output schemas, errors, and metadata) without any business logic.
@see{@link https://orpc.dev/docs/contract/procedure Procedure Contract}
oc
.ContractBuilder<object>.output<z.ZodString>(schema: z.ZodString): ProcedureContractBuilderWithOutput<z.ZodString, object>output(import zz.function string(params?: string | z.core.$ZodStringParams): z.ZodString (+1 overload)string())
export const
const router: {
    ping: ProcedureContractBuilderWithOutput<z.ZodString, object>;
    pong: ProcedureContractBuilderWithOutput<z.ZodString, object>;
    nested: {
        ping: ProcedureContractBuilderWithOutput<z.ZodString, object>;
        pong: ProcedureContractBuilderWithOutput<z.ZodString, object>;
    };
}
router
= {
ping: ProcedureContractBuilderWithOutput<z.ZodString, object>ping, pong: ProcedureContractBuilderWithOutput<z.ZodString, object>pong,
nested: {
    ping: ProcedureContractBuilderWithOutput<z.ZodString, object>;
    pong: ProcedureContractBuilderWithOutput<z.ZodString, object>;
}
nested
: { ping: ProcedureContractBuilderWithOutput<z.ZodString, object>ping, pong: ProcedureContractBuilderWithOutput<z.ZodString, object>pong }
}

Extending Router

You can extend a router contract with shared configuration, such as attaching metadata to every procedure:

const router = oc.meta(requireAuthMeta).router({
  ping,
  pong,
  nested: {
    ping,
    pong,
  }
})

Router to Contract

A normal router can be used as a contract router as long as it does not include a lazy router. If necessary, use unlazyRouter to fully resolve it and make it contract-compatible.

import { unlazyRouter } from '@orpc/server'

const compatibleContract = await unlazyRouter(router)

Safely Importing Router on the Client

Sometimes you need to import the contract on the client, for example when using OpenAPI Link. If you derive the contract from a router, importing it directly can be heavy and may expose internal logic. To avoid this, follow the steps below to safely minify and export the contract.

  1. Minify the Contract Router and Export to JSON

    import fs from 'node:fs'
    import { unlazyRouter } from '@orpc/server'
    import { minifyRouterContract } from '@orpc/contract'
    
    const compatibleContract = await unlazyRouter(router)
    const minifiedRouter = minifyRouterContract(compatibleContract)
    
    fs.writeFileSync('./contract.json', JSON.stringify(minifiedRouter))

    ::: info minifyRouterContract preserves only the metadata needed by the client; all other data is stripped out. :::

  2. Import the Contract JSON on the Client Side

    import contract from './contract.json'
    
    const link = new OpenAPILink(contract as typeof router)

    ::: info Cast contract to typeof router to preserve type safety, since standard schema types cannot be serialized to JSON and must be cast manually. :::

Utilities

Infer Router Contract Inputs

Infers the input type of each procedure contract in a router contract.

import type { type InferRouterContractInputs<T extends RouterContract> = T extends ProcedureContract<infer UInputSchema extends AnySchema, any, any> ? InferSchemaInput<UInputSchema> : { [K in keyof T]: T[K] extends RouterContract ? InferRouterContractInputs<T[K]> : never; }
Infer the input types for each procedure-contract, preserving the router-contract shape.
@see{@link https://orpc.dev/docs/contract/router#infer-router-contract-inputs Router Contract - Infer Router Contract Inputs}
InferRouterContractInputs
} from '@orpc/contract'
export type
type Inputs = {
    planet: {
        list: {
            limit?: number | undefined;
            cursor?: number | undefined;
        };
        find: {
            id: number;
        };
        create: {
            name: string;
            description?: string | undefined;
        };
    };
}
Inputs
= type InferRouterContractInputs<T extends RouterContract> = T extends ProcedureContract<infer UInputSchema extends AnySchema, any, any> ? InferSchemaInput<UInputSchema> : { [K in keyof T]: T[K] extends RouterContract ? InferRouterContractInputs<T[K]> : never; }
Infer the input types for each procedure-contract, preserving the router-contract shape.
@see{@link https://orpc.dev/docs/contract/router#infer-router-contract-inputs Router Contract - Infer Router Contract Inputs}
InferRouterContractInputs
<typeof
const contract: {
    planet: {
        list: ProcedureContractBuilderWithInputOutput<ZodObject<{
            limit: ZodOptional<ZodNumber>;
            cursor: ZodDefault<ZodNumber>;
        }, $strip>, ZodArray<ZodObject<{
            id: ZodNumber;
            name: ZodString;
            description: ZodOptional<ZodString>;
        }, $strip>>, object>;
        find: ProcedureContractBuilderWithInputOutput<ZodObject<{
            id: ZodNumber;
        }, $strip>, ZodObject<{
            id: ZodNumber;
            name: ZodString;
            description: ZodOptional<ZodString>;
        }, $strip>, object>;
        create: ProcedureContractBuilderWithInputOutput<...>;
    };
}
contract
>
type
type FindPlanetInput = {
    id: number;
}
FindPlanetInput
=
type Inputs = {
    planet: {
        list: {
            limit?: number | undefined;
            cursor?: number | undefined;
        };
        find: {
            id: number;
        };
        create: {
            name: string;
            description?: string | undefined;
        };
    };
}
Inputs
['planet']['find']

Infer Router Contract Outputs

Infers the output type of each procedure contract in a router contract.

import type { type InferRouterContractOutputs<T extends RouterContract> = T extends ProcedureContract<any, infer UOutputSchema extends AnySchema, any> ? InferSchemaOutput<UOutputSchema> : { [K in keyof T]: T[K] extends RouterContract ? InferRouterContractOutputs<T[K]> : never; }
Infer the output types for each procedure-contract, preserving the router-contract shape.
@see{@link https://orpc.dev/docs/contract/router#infer-router-contract-outputs Router Contract - Infer Router Contract Outputs}
InferRouterContractOutputs
} from '@orpc/contract'
export type
type Outputs = {
    planet: {
        list: {
            id: number;
            name: string;
            description?: string | undefined;
        }[];
        find: {
            id: number;
            name: string;
            description?: string | undefined;
        };
        create: {
            id: number;
            name: string;
            description?: string | undefined;
        };
    };
}
Outputs
= type InferRouterContractOutputs<T extends RouterContract> = T extends ProcedureContract<any, infer UOutputSchema extends AnySchema, any> ? InferSchemaOutput<UOutputSchema> : { [K in keyof T]: T[K] extends RouterContract ? InferRouterContractOutputs<T[K]> : never; }
Infer the output types for each procedure-contract, preserving the router-contract shape.
@see{@link https://orpc.dev/docs/contract/router#infer-router-contract-outputs Router Contract - Infer Router Contract Outputs}
InferRouterContractOutputs
<typeof
const contract: {
    planet: {
        list: ProcedureContractBuilderWithInputOutput<ZodObject<{
            limit: ZodOptional<ZodNumber>;
            cursor: ZodDefault<ZodNumber>;
        }, $strip>, ZodArray<ZodObject<{
            id: ZodNumber;
            name: ZodString;
            description: ZodOptional<ZodString>;
        }, $strip>>, object>;
        find: ProcedureContractBuilderWithInputOutput<ZodObject<{
            id: ZodNumber;
        }, $strip>, ZodObject<{
            id: ZodNumber;
            name: ZodString;
            description: ZodOptional<ZodString>;
        }, $strip>, object>;
        create: ProcedureContractBuilderWithInputOutput<...>;
    };
}
contract
>
type
type FindPlanetOutput = {
    id: number;
    name: string;
    description?: string | undefined;
}
FindPlanetOutput
=
type Outputs = {
    planet: {
        list: {
            id: number;
            name: string;
            description?: string | undefined;
        }[];
        find: {
            id: number;
            name: string;
            description?: string | undefined;
        };
        create: {
            id: number;
            name: string;
            description?: string | undefined;
        };
    };
}
Outputs
['planet']['find']

Infer Router Contract Error Map

Collects the error maps from every procedure contract in a router contract into a single type.

import type { type InferRouterContractErrorMap<T extends RouterContract> = T extends ProcedureContract<any, any, infer UErrorMap extends ErrorMap> ? UErrorMap : { [K in keyof T]: T[K] extends RouterContract ? InferRouterContractErrorMap<T[K]> : never; }[keyof T]
Infer the union of error maps defined across the entire router-contract.
@see{@link https://orpc.dev/docs/contract/router#infer-router-contract-error-map Router Contract - Infer Router Contract Error Map}
InferRouterContractErrorMap
} from '@orpc/contract'
export type type ErrorMap = objectErrorMap = type InferRouterContractErrorMap<T extends RouterContract> = T extends ProcedureContract<any, any, infer UErrorMap extends ErrorMap> ? UErrorMap : { [K in keyof T]: T[K] extends RouterContract ? InferRouterContractErrorMap<T[K]> : never; }[keyof T]
Infer the union of error maps defined across the entire router-contract.
@see{@link https://orpc.dev/docs/contract/router#infer-router-contract-error-map Router Contract - Infer Router Contract Error Map}
InferRouterContractErrorMap
<typeof
const contract: {
    planet: {
        list: ProcedureContractBuilderWithInputOutput<ZodObject<{
            limit: ZodOptional<ZodNumber>;
            cursor: ZodDefault<ZodNumber>;
        }, $strip>, ZodArray<ZodObject<{
            id: ZodNumber;
            name: ZodString;
            description: ZodOptional<ZodString>;
        }, $strip>>, object>;
        find: ProcedureContractBuilderWithInputOutput<ZodObject<{
            id: ZodNumber;
        }, $strip>, ZodObject<{
            id: ZodNumber;
            name: ZodString;
            description: ZodOptional<ZodString>;
        }, $strip>, object>;
        create: ProcedureContractBuilderWithInputOutput<...>;
    };
}
contract
>

Infer Router Contract Errors

Infers the throwable errors each procedure contract in a router contract can describe.

import type { type InferRouterContractErrors<T extends RouterContract> = T extends ProcedureContract<any, any, infer UErrorMap extends ErrorMap> ? ORPCErrorFromErrorMap<UErrorMap> | Error : { [K in keyof T]: T[K] extends RouterContract ? InferRouterContractErrors<T[K]> : never; }
Infer throwable errors for each procedure-contract, preserving the router-contract shape.
@see{@link https://orpc.dev/docs/contract/router#infer-router-contract-errors Router Contract - Infer Router Contract Errors}
InferRouterContractErrors
} from '@orpc/contract'
export type
type Errors = {
    planet: {
        list: Error;
        find: Error;
        create: Error;
    };
}
Errors
= type InferRouterContractErrors<T extends RouterContract> = T extends ProcedureContract<any, any, infer UErrorMap extends ErrorMap> ? ORPCErrorFromErrorMap<UErrorMap> | Error : { [K in keyof T]: T[K] extends RouterContract ? InferRouterContractErrors<T[K]> : never; }
Infer throwable errors for each procedure-contract, preserving the router-contract shape.
@see{@link https://orpc.dev/docs/contract/router#infer-router-contract-errors Router Contract - Infer Router Contract Errors}
InferRouterContractErrors
<typeof
const contract: {
    planet: {
        list: ProcedureContractBuilderWithInputOutput<ZodObject<{
            limit: ZodOptional<ZodNumber>;
            cursor: ZodDefault<ZodNumber>;
        }, $strip>, ZodArray<ZodObject<{
            id: ZodNumber;
            name: ZodString;
            description: ZodOptional<ZodString>;
        }, $strip>>, object>;
        find: ProcedureContractBuilderWithInputOutput<ZodObject<{
            id: ZodNumber;
        }, $strip>, ZodObject<{
            id: ZodNumber;
            name: ZodString;
            description: ZodOptional<ZodString>;
        }, $strip>, object>;
        create: ProcedureContractBuilderWithInputOutput<...>;
    };
}
contract
>
type type FindPlanetError = ErrorFindPlanetError =
type Errors = {
    planet: {
        list: Error;
        find: Error;
        create: Error;
    };
}
Errors
['planet']['find']

Infer Router Contract Error

Infers all possible throwable errors the entire router contract can describe. This is useful when you want a single type for contract-wide error handling.

import type { type InferRouterContractError<T extends RouterContract> = T extends ProcedureContract<any, any, infer UErrorMap extends ErrorMap> ? ORPCErrorFromErrorMap<UErrorMap> | Error : { [K in keyof T]: T[K] extends RouterContract ? InferRouterContractError<T[K]> : never; }[keyof T]
Infer the union of throwable errors for entire router-contract.
@see{@link https://orpc.dev/docs/contract/router#infer-router-contract-error Router Contract - Infer Router Contract Error}
InferRouterContractError
} from '@orpc/contract'
export type type ContractError = ErrorContractError = type InferRouterContractError<T extends RouterContract> = T extends ProcedureContract<any, any, infer UErrorMap extends ErrorMap> ? ORPCErrorFromErrorMap<UErrorMap> | Error : { [K in keyof T]: T[K] extends RouterContract ? InferRouterContractError<T[K]> : never; }[keyof T]
Infer the union of throwable errors for entire router-contract.
@see{@link https://orpc.dev/docs/contract/router#infer-router-contract-error Router Contract - Infer Router Contract Error}
InferRouterContractError
<typeof
const contract: {
    planet: {
        list: ProcedureContractBuilderWithInputOutput<ZodObject<{
            limit: ZodOptional<ZodNumber>;
            cursor: ZodDefault<ZodNumber>;
        }, $strip>, ZodArray<ZodObject<{
            id: ZodNumber;
            name: ZodString;
            description: ZodOptional<ZodString>;
        }, $strip>>, object>;
        find: ProcedureContractBuilderWithInputOutput<ZodObject<{
            id: ZodNumber;
        }, $strip>, ZodObject<{
            id: ZodNumber;
            name: ZodString;
            description: ZodOptional<ZodString>;
        }, $strip>, object>;
        create: ProcedureContractBuilderWithInputOutput<...>;
    };
}
contract
>

Last updated on August 6, 2026

Was this page helpful?