Skip to content
oRPC
Esc
navigateopen⌘Jpreview
On this page

Router

A router is a plain, nestable object made up of procedures. Routers can also modify those procedures, which makes it easy to organize and extend your API.

Overview

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

import { 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
} from '@orpc/server'
const const ping: DecoratedProcedure<DefaultInitialContext & object, object, InitialInputSchema, Schema<string>, Record<never, never>, never>ping = 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>>.handler<string>(handler: ProcedureHandler<DefaultInitialContext & object, unknown, string, ORPCErrorConstructorMap<Record<never, never>>>): DecoratedProcedure<DefaultInitialContext & object, object, InitialInputSchema, Schema<string>, Record<never, never>, never>handler(async () => 'ping')
const const pong: DecoratedProcedure<DefaultInitialContext & object, object, InitialInputSchema, Schema<string>, Record<never, never>, never>pong = 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>>.handler<string>(handler: ProcedureHandler<DefaultInitialContext & object, unknown, string, ORPCErrorConstructorMap<Record<never, never>>>): DecoratedProcedure<DefaultInitialContext & object, object, InitialInputSchema, Schema<string>, Record<never, never>, never>handler(async () => 'pong')
export const
const router: {
    ping: DecoratedProcedure<DefaultInitialContext & object, object, InitialInputSchema, Schema<string>, Record<never, never>, never>;
    pong: DecoratedProcedure<DefaultInitialContext & object, object, InitialInputSchema, Schema<string>, Record<never, never>, never>;
    nested: {
        ping: DecoratedProcedure<DefaultInitialContext & object, object, InitialInputSchema, Schema<...>, Record<...>, never>;
        pong: DecoratedProcedure<...>;
    };
}
router
= {
ping: DecoratedProcedure<DefaultInitialContext & object, object, InitialInputSchema, Schema<string>, Record<never, never>, never>ping, pong: DecoratedProcedure<DefaultInitialContext & object, object, InitialInputSchema, Schema<string>, Record<never, never>, never>pong,
nested: {
    ping: DecoratedProcedure<DefaultInitialContext & object, object, InitialInputSchema, Schema<string>, Record<never, never>, never>;
    pong: DecoratedProcedure<DefaultInitialContext & object, object, InitialInputSchema, Schema<string>, Record<never, never>, never>;
}
nested
: { ping: DecoratedProcedure<DefaultInitialContext & object, object, InitialInputSchema, Schema<string>, Record<never, never>, never>ping, pong: DecoratedProcedure<DefaultInitialContext & object, object, InitialInputSchema, Schema<string>, Record<never, never>, never>pong }
}

Extending Router

You can extend a router with shared behavior. For example, by applying authentication middleware or attaching metadata to every procedure:

const router = os.use(requiredAuth).meta(requireAuthMeta).router({
  ping,
  pong,
  nested: {
    ping,
    pong,
  }
})

Lazy Router

Routers can also be lazy-loaded. This is useful for code splitting and can improve cold start performance by deferring route initialization until it is needed.

const router = {
  ping,
  pong,
  planet: os.lazy(() => import('./planet'))
}
const PlanetSchema = z.object({
  id: z.number().int().min(1),
  name: z.string(),
  description: z.string().optional(),
})

export const listPlanet = os
  .input(
    z.object({
      limit: z.number().int().min(1).max(100).optional(),
      cursor: z.number().int().min(0).default(0),
    }),
  )
  .handler(async ({ input }) => {
    // your list code here
    return [{ id: 1, name: 'name' }]
  })

export default {
  list: listPlanet,
  // ...
}

Utilities

Infer Router Inputs

Infers the input type for each procedure in the router.

import type { type InferRouterInputs<T extends AnyRouter> = T extends Procedure<any, any, infer UInputSchema extends AnySchema, any, any, any> ? InferSchemaInput<UInputSchema> : { [K in keyof T]: T[K] extends Lazyable<infer U extends AnyRouter> ? InferRouterInputs<U> : never; }
Infer all router inputs.
@remarks**Note**: A procedure is a router too.@see{@link https://orpc.dev/docs/router#infer-router-inputs Router - Infer Router Inputs}
InferRouterInputs
} from '@orpc/server'
export type
type Inputs = {
    planet: {
        list: {
            limit?: number | undefined;
            cursor?: number | undefined;
        };
        find: {
            id: number;
        };
        create: {
            name: string;
            description?: string | undefined;
        };
    };
}
Inputs
= type InferRouterInputs<T extends AnyRouter> = T extends Procedure<any, any, infer UInputSchema extends AnySchema, any, any, any> ? InferSchemaInput<UInputSchema> : { [K in keyof T]: T[K] extends Lazyable<infer U extends AnyRouter> ? InferRouterInputs<U> : never; }
Infer all router inputs.
@remarks**Note**: A procedure is a router too.@see{@link https://orpc.dev/docs/router#infer-router-inputs Router - Infer Router Inputs}
InferRouterInputs
<typeof
const router: {
    planet: {
        list: ImplementedProcedure<{
            headers?: IncomingHttpHeaders;
        } & object, object, ZodObject<{
            limit: ZodOptional<ZodNumber>;
            cursor: ZodDefault<ZodNumber>;
        }, $strip>, ZodArray<ZodObject<{
            id: ZodNumber;
            name: ZodString;
            description: ZodOptional<ZodString>;
        }, $strip>>, object>;
        find: ImplementedProcedure<{
            headers?: IncomingHttpHeaders;
        } & object, object, ZodObject<{
            id: ZodNumber;
        }, $strip>, ZodObject<...>, object>;
        create: ImplementedProcedure<...>;
    };
}
router
>
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 Outputs

Infers the output type for each procedure in the router.

import type { type InferRouterOutputs<T extends AnyRouter> = T extends Procedure<any, any, any, infer UOutputSchema extends AnySchema, any, any> ? InferSchemaOutput<UOutputSchema> : { [K in keyof T]: T[K] extends Lazyable<infer U extends AnyRouter> ? InferRouterOutputs<U> : never; }
Infer all router outputs.
@remarks**Note**: A procedure is a router too.@see{@link https://orpc.dev/docs/router#infer-router-outputs Router - Infer Router Outputs}
InferRouterOutputs
} from '@orpc/server'
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 InferRouterOutputs<T extends AnyRouter> = T extends Procedure<any, any, any, infer UOutputSchema extends AnySchema, any, any> ? InferSchemaOutput<UOutputSchema> : { [K in keyof T]: T[K] extends Lazyable<infer U extends AnyRouter> ? InferRouterOutputs<U> : never; }
Infer all router outputs.
@remarks**Note**: A procedure is a router too.@see{@link https://orpc.dev/docs/router#infer-router-outputs Router - Infer Router Outputs}
InferRouterOutputs
<typeof
const router: {
    planet: {
        list: ImplementedProcedure<{
            headers?: IncomingHttpHeaders;
        } & object, object, ZodObject<{
            limit: ZodOptional<ZodNumber>;
            cursor: ZodDefault<ZodNumber>;
        }, $strip>, ZodArray<ZodObject<{
            id: ZodNumber;
            name: ZodString;
            description: ZodOptional<ZodString>;
        }, $strip>>, object>;
        find: ImplementedProcedure<{
            headers?: IncomingHttpHeaders;
        } & object, object, ZodObject<{
            id: ZodNumber;
        }, $strip>, ZodObject<...>, object>;
        create: ImplementedProcedure<...>;
    };
}
router
>
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 Initial Contexts

Infers the initial context for each procedure in the router.

import type { type InferRouterInitialContexts<T extends AnyRouter> = T extends Procedure<infer UInitialContext extends Context, any, any, any, any, any> ? UInitialContext : { [K in keyof T]: T[K] extends Lazyable<infer U extends AnyRouter> ? InferRouterInitialContexts<U> : never; }
Infer all initial context of the router.
@remarks**Note**: A procedure is a router too.@see{@link https://orpc.dev/docs/router#infer-router-initial-contexts Router - Infer Router Initial Contexts}
InferRouterInitialContexts
} from '@orpc/server'
export type
type InitialContexts = {
    planet: {
        list: {
            headers?: IncomingHttpHeaders;
        } & object;
        find: {
            headers?: IncomingHttpHeaders;
        } & object;
        create: {
            headers?: IncomingHttpHeaders;
        } & object;
    };
}
InitialContexts
= type InferRouterInitialContexts<T extends AnyRouter> = T extends Procedure<infer UInitialContext extends Context, any, any, any, any, any> ? UInitialContext : { [K in keyof T]: T[K] extends Lazyable<infer U extends AnyRouter> ? InferRouterInitialContexts<U> : never; }
Infer all initial context of the router.
@remarks**Note**: A procedure is a router too.@see{@link https://orpc.dev/docs/router#infer-router-initial-contexts Router - Infer Router Initial Contexts}
InferRouterInitialContexts
<typeof
const router: {
    planet: {
        list: ImplementedProcedure<{
            headers?: IncomingHttpHeaders;
        } & object, object, ZodObject<{
            limit: ZodOptional<ZodNumber>;
            cursor: ZodDefault<ZodNumber>;
        }, $strip>, ZodArray<ZodObject<{
            id: ZodNumber;
            name: ZodString;
            description: ZodOptional<ZodString>;
        }, $strip>>, object>;
        find: ImplementedProcedure<{
            headers?: IncomingHttpHeaders;
        } & object, object, ZodObject<{
            id: ZodNumber;
        }, $strip>, ZodObject<...>, object>;
        create: ImplementedProcedure<...>;
    };
}
router
>
type
type FindPlanetInitialContext = {
    headers?: IncomingHttpHeaders;
} & object
FindPlanetInitialContext
=
type InitialContexts = {
    planet: {
        list: {
            headers?: IncomingHttpHeaders;
        } & object;
        find: {
            headers?: IncomingHttpHeaders;
        } & object;
        create: {
            headers?: IncomingHttpHeaders;
        } & object;
    };
}
InitialContexts
['planet']['find']

Infer Router Final Contexts

Infers the final context for each procedure in the router by combining the initial and injected context. This is the closest match to the context the procedure’s handler receives.

import type { type InferRouterFinalContexts<T extends AnyRouter> = T extends Procedure<infer UInitialContext extends Context, infer UInjectedContext extends Context, any, any, any, any> ? MergedContext<UInitialContext, UInjectedContext> : { [K in keyof T]: T[K] extends Lazyable<infer U extends AnyRouter> ? InferRouterFinalContexts<U> : never; }
Infer all current context of the router.
@remarks**Note**: A procedure is a router too.@see{@link https://orpc.dev/docs/router#infer-router-final-contexts Router - Infer Router Final Contexts}
InferRouterFinalContexts
} from '@orpc/server'
export type
type FinalContexts = {
    planet: {
        list: {
            headers?: IncomingHttpHeaders;
        } & object;
        find: {
            headers?: IncomingHttpHeaders;
        } & object;
        create: {
            headers?: IncomingHttpHeaders;
        } & object;
    };
}
FinalContexts
= type InferRouterFinalContexts<T extends AnyRouter> = T extends Procedure<infer UInitialContext extends Context, infer UInjectedContext extends Context, any, any, any, any> ? MergedContext<UInitialContext, UInjectedContext> : { [K in keyof T]: T[K] extends Lazyable<infer U extends AnyRouter> ? InferRouterFinalContexts<U> : never; }
Infer all current context of the router.
@remarks**Note**: A procedure is a router too.@see{@link https://orpc.dev/docs/router#infer-router-final-contexts Router - Infer Router Final Contexts}
InferRouterFinalContexts
<typeof
const router: {
    planet: {
        list: ImplementedProcedure<{
            headers?: IncomingHttpHeaders;
        } & object, object, ZodObject<{
            limit: ZodOptional<ZodNumber>;
            cursor: ZodDefault<ZodNumber>;
        }, $strip>, ZodArray<ZodObject<{
            id: ZodNumber;
            name: ZodString;
            description: ZodOptional<ZodString>;
        }, $strip>>, object>;
        find: ImplementedProcedure<{
            headers?: IncomingHttpHeaders;
        } & object, object, ZodObject<{
            id: ZodNumber;
        }, $strip>, ZodObject<...>, object>;
        create: ImplementedProcedure<...>;
    };
}
router
>
type
type FindPlanetFinalContext = {
    headers?: IncomingHttpHeaders;
} & object
FindPlanetFinalContext
=
type FinalContexts = {
    planet: {
        list: {
            headers?: IncomingHttpHeaders;
        } & object;
        find: {
            headers?: IncomingHttpHeaders;
        } & object;
        create: {
            headers?: IncomingHttpHeaders;
        } & object;
    };
}
FinalContexts
['planet']['find']

Infer Router Errors

Infers the throwable errors each procedure in a router can produce.

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

Infer Router Error

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

import type { type InferRouterError<T extends AnyRouter> = T extends Procedure<any, any, any, any, infer UErrorMap extends ErrorMap, infer UReturnedError extends AnyORPCError> ? UReturnedError | ORPCErrorFromErrorMap<UErrorMap> | Error : { [K in keyof T]: T[K] extends Lazyable<infer U extends AnyRouter> ? InferRouterError<U> : never; }[keyof T]
Infer the union of throwable errors for entire router.
@see{@link https://orpc.dev/docs/router#infer-router-error Router - Infer Router Error}
InferRouterError
} from '@orpc/server'
export type type RouterError = ErrorRouterError = type InferRouterError<T extends AnyRouter> = T extends Procedure<any, any, any, any, infer UErrorMap extends ErrorMap, infer UReturnedError extends AnyORPCError> ? UReturnedError | ORPCErrorFromErrorMap<UErrorMap> | Error : { [K in keyof T]: T[K] extends Lazyable<infer U extends AnyRouter> ? InferRouterError<U> : never; }[keyof T]
Infer the union of throwable errors for entire router.
@see{@link https://orpc.dev/docs/router#infer-router-error Router - Infer Router Error}
InferRouterError
<typeof
const router: {
    planet: {
        list: ImplementedProcedure<{
            headers?: IncomingHttpHeaders;
        } & object, object, ZodObject<{
            limit: ZodOptional<ZodNumber>;
            cursor: ZodDefault<ZodNumber>;
        }, $strip>, ZodArray<ZodObject<{
            id: ZodNumber;
            name: ZodString;
            description: ZodOptional<ZodString>;
        }, $strip>>, object>;
        find: ImplementedProcedure<{
            headers?: IncomingHttpHeaders;
        } & object, object, ZodObject<{
            id: ZodNumber;
        }, $strip>, ZodObject<...>, object>;
        create: ImplementedProcedure<...>;
    };
}
router
>

Last updated on August 6, 2026

Was this page helpful?