GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • oRPC
  • Documentation
  • Blog
  • Comparison
  • From V1
  • Sponsors
  • Experts
  • Releases
  • Discussions
  • LLM Context
  • LLM Context (Full)
  • V1 Documentation
  • Contract-First
  • Requirements

This view

  • Original page
  • New address

GeezerVison is showing orpc.dev — OpenAPI Routing - oRPC

On this page
  • Basic Routing
  • Path Parameters
  • Prefixes
  • Path Parameters in Prefixes
  • Lazy Router
  • Metadata Merging
  • Shorthands
  • .route extension

OpenAPI Routing

Use openapi metadata to control how procedures are exposed over HTTP, including methods, paths, path parameters, and prefixes.

Sponsors

ScreenshotOne.comThe screenshot API for developersYuzuWe're hiring NYC based engineersMisskeyHQDecentralized microblogging SNS born on Earth

Basic Routing

If you do not set OpenAPI routing metadata, a procedure is exposed as a POST endpoint whose path is derived from the router structure. For example:

import { 
module openapi
const openapi: OpenAPIFunction
Creates OpenAPI meta plugins that control how a procedure is exposed over HTTP, such as its method, path, prefix, and OpenAPI operation spec.
@see{@link https://orpc.dev/docs/openapi/routing OpenAPI Routing}@see{@link https://orpc.dev/docs/openapi/specification OpenAPI Specification}
openapi
} from '@orpc/openapi'
const
const router: {
    planet: {
        list: DecoratedProcedure<DefaultInitialContext & object, object, InitialInputSchema, Schema<{
            id: string;
            name: string;
        }[]>, Record<never, never>>;
        create: DecoratedProcedure<DefaultInitialContext & object, object, InitialInputSchema, Schema<{}>, Record<never, never>>;
    };
}
router
= {
planet: {
    list: DecoratedProcedure<DefaultInitialContext & object, object, InitialInputSchema, Schema<{
        id: string;
        name: string;
    }[]>, Record<never, never>>;
    create: DecoratedProcedure<DefaultInitialContext & object, object, InitialInputSchema, Schema<{}>, Record<never, never>>;
}
planet
: {
list: DecoratedProcedure<DefaultInitialContext & object, object, InitialInputSchema, Schema<{
    id: string;
    name: string;
}[]>, Record<never, never>>
list
: 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>>['meta'](...plugins: MetaPlugin<InitialInputSchema, InitialOutputSchema, Record<never, never>>[]): Builder<DefaultInitialContext & object, Record<never, never>>
Applies metadata plugins to procedures built from this builder.
@see{@link https://orpc.dev/docs/metadata Metadata}
meta
(function openapi(meta: OpenAPIMeta): OpenAPIMetaPlugin<any, any, any>
Creates OpenAPI meta plugins that control how a procedure is exposed over HTTP, such as its method, path, prefix, and OpenAPI operation spec.
@see{@link https://orpc.dev/docs/openapi/routing OpenAPI Routing}@see{@link https://orpc.dev/docs/openapi/specification OpenAPI Specification}
openapi
({ OpenAPIMeta.method?: "GET" | "HEAD" | "POST" | "PUT" | "DELETE" | "PATCH" | "QUERY" | undefined
HTTP method accepted by this procedure.
@default'POST'
method
: 'GET', OpenAPIMeta.path?: `/${string}` | undefined
URL path for this procedure. Supports dynamic parameters via `{param}` syntax, and `{+param}` to allow slashes in the matched value.
@example`/users`, `/users/{id}`, `/files/{+path}`@defaultRouter segments joined by `'/`
path
: '/planets' }))
.
Builder<DefaultInitialContext & object, Record<never, never>>['handler']<{
    id: string;
    name: string;
}[]>(handler: ProcedureHandler<DefaultInitialContext & object, unknown, {
    id: string;
    name: string;
}[], ORPCErrorConstructorMap<Record<never, never>>>): DecoratedProcedure<DefaultInitialContext & object, object, InitialInputSchema, Schema<{
    id: string;
    name: string;
}[]>, Record<never, never>>
Defines the function that implements the procedure and completes the chain, returning a callable procedure.
@see{@link https://orpc.dev/docs/procedure Procedure}
handler
(async () => [{ id: stringid: 'earth', name: stringname: 'Earth' }]),
create: DecoratedProcedure<DefaultInitialContext & object, object, InitialInputSchema, Schema<{}>, Record<never, never>>create: 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']<{}>(handler: ProcedureHandler<DefaultInitialContext & object, unknown, {}, ORPCErrorConstructorMap<Record<never, never>>>): DecoratedProcedure<DefaultInitialContext & object, object, InitialInputSchema, Schema<{}>, Record<never, never>>
Defines the function that implements the procedure and completes the chain, returning a callable procedure.
@see{@link https://orpc.dev/docs/procedure Procedure}
handler
(async () => ({})),
}, }

In this example, list is exposed as GET /planets because it overrides the default method and path. create keeps the default behavior, so it is exposed as POST /planet/create.

Path Parameters

To define a path parameter, use {name} in the path and add the same field as a required key in the input schema:

import { z } from 'zod'

const getPlanet = os
  .meta(openapi({ method: 'GET', path: '/planets/{id}' }))
  .input(z.object({ id: z.string() }))

For catch-all path segments that may include /, use {+name}:

const getFile = os
  .meta(openapi({ method: 'GET', path: '/files/{+path}' }))
  .input(z.object({ path: z.string() }))

A path can have only one {+name}, but more segments can follow it, as in /files/{+path}/raw.

To customize path parameter encoding and decoding, see Path Parameter Styles.

Prefixes

Define prefix to prepend a path to a procedure, or an entire router:

const planetBuilder = os.meta(openapi({ prefix: '/planets' }))

const listPlanets = planetBuilder
  .meta(openapi({ method: 'GET', path: '/' }))
  .handler(async () => [{ id: 'earth', name: 'Earth' }])

const createPlanet = planetBuilder.handler(async () => ({}))

const router = os.meta(openapi({ prefix: '/api/v2' })).router({
  planet: {
    list: listPlanets,
    create: createPlanet,
  },
})

In this example, listPlanets is exposed as GET /api/v2/planets/. createPlanet is exposed as POST /api/v2/planets/planet/create.

Path Parameters in Prefixes

Prefixes can also include path parameters, but they must be defined as required fields in the input schema.

const base = os
  .meta(openapi({ prefix: '/{workspaceId}' }))
  .input(z.object({ workspaceId: z.string() }))
  .use(({ next }, { workspaceId }) => {
    console.log('Workspace ID:', workspaceId)
    return next()
  })

const procedure = base
  .meta(openapi({ method: 'GET', path: '/planets/{id}' }))
  .input(z.object({ id: z.string() }))
  .handler(async ({ input }) => {
    console.log('Workspace ID:', input.workspaceId)
    console.log('Planet ID:', input.id)
  })

Lazy Router

When using a lazy router, define a prefix so lazy loading is triggered only for relevant requests:

const router = {
  project: os.meta(openapi({ prefix: '/projects' })).lazy(() => import('./project')),
}

Sponsors

ScreenshotOne.comThe screenshot API for developersYuzuWe're hiring NYC based engineers

Metadata Merging

When openapi is applied multiple times, prefix values are concatenated in definition order, while method, path, and successStatus are overridden by the most recent call. For the full merge behavior of every field, see Metadata Merging.

A router-level prefix applies again, even if a procedure already has it:

const base = os.meta(openapi({ prefix: '/api' }))

const router = base.router({
  ping: base.handler(() => 'pong'), // prefix: '/api/api'
})
const router = os.meta(openapi({ prefix: '/api/v2' })).router({
  get: os
    .meta(openapi({ prefix: '/planets' }))
    .meta(openapi({ method: 'GET', path: '/planets/{id}' }))
    .meta(openapi({ path: '/{id}' }))
    .input(z.object({ id: z.string() }))
    .handler(async () => ({})),
})

These calls are equivalent to:

const router = {
  get: os
    .meta(
      openapi({
        prefix: '/api/v2/planets',
        method: 'GET',
        path: '/{id}',
      }),
    )
    .handler(async () => ({})),
}

Metadata resets to its default behavior when set to undefined in subsequent calls:

const example = os.meta(openapi({ prefix: '/api/v2' })).meta(openapi({ prefix: undefined }))

In this example, the final prefix is undefined, so no prefix is applied to example.

Shorthands

For common cases, use the shorthand helpers:

const listPlanets = os
  .meta(openapi.prefix('/planets'))
  .meta(openapi.method('GET'))
  .meta(openapi.path('/'))

.route extension

Import @orpc/openapi/extensions/route from a module that always runs during initialization, such as the file where you define your base builder or create your server. This adds a .route method to the builder, allowing you to define OpenAPI metadata directly without wrapping it in .meta(openapi(...)).

const ping = base
  .route({
    method: 'GET',
    path: '/ping',
  })
  .input(z.object({ name: z.string() }))
  .handler(async ({ input }) => {
    return `Hello ${input.name}!`
  })
import '@orpc/openapi/extensions/route'

import { os } from '@orpc/server'

export const base = os

Last updated on November 9, 2026

Was this page helpful?

Thanks for your feedback!

You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.