Skip to main content
Install @orpc/contract:
npm install @orpc/contract

The oc builder

@orpc/contract exports a pre-configured oc builder (ContractBuilder) that mirrors the os builder API but only defines the contract shape — no handlers, no middleware.
import { oc } from '@orpc/contract'

Procedure contracts

.input(schema)

Defines the input schema:
import { oc } from '@orpc/contract'
import * as z from 'zod'

const listPlanets = oc
  .input(
    z.object({
      limit: z.number().int().min(1).max(100).optional(),
      cursor: z.number().int().min(0).default(0),
    }),
  )

.output(schema)

Defines the output schema:
const listPlanets = oc
  .input(z.object({ limit: z.number().optional() }))
  .output(z.array(z.object({ id: z.number(), name: z.string() })))

.errors(map)

Declares typed errors the procedure can return:
const findPlanet = oc
  .input(z.object({ id: z.number() }))
  .output(z.object({ id: z.number(), name: z.string() }))
  .errors({
    NOT_FOUND: {
      status: 404,
      message: 'Planet not found',
    },
  })

.route(options)

Attaches HTTP route information for OpenAPI generation:
const findPlanet = oc
  .route({
    method: 'GET',
    path: '/planets/{id}',
    summary: 'Find a planet by ID',
    tags: ['planets'],
  })
  .input(z.object({ id: z.number() }))
  .output(z.object({ id: z.number(), name: z.string() }))

.meta(object)

Attaches arbitrary metadata (useful for authorization annotations, etc.):
const createPlanet = oc
  .$meta<{ authRequired: boolean }>({})
  .meta({ authRequired: true })
  .input(z.object({ name: z.string() }))
  .output(z.object({ id: z.number(), name: z.string() }))

Contract routers

Group procedure contracts into a nested object using oc.router():
import { oc } from '@orpc/contract'
import * as z from 'zod'

export const contract = oc.router({
  planet: {
    list: oc
      .input(z.object({ limit: z.number().optional() }))
      .output(z.array(z.object({ id: z.number(), name: z.string() }))),

    find: oc
      .route({ method: 'GET', path: '/planets/{id}' })
      .input(z.object({ id: z.number() }))
      .output(z.object({ id: z.number(), name: z.string() }))
      .errors({ NOT_FOUND: { status: 404 } }),

    create: oc
      .route({ method: 'POST', path: '/planets' })
      .input(z.object({ name: z.string() }))
      .output(z.object({ id: z.number(), name: z.string() })),
  },
})

export type AppContract = typeof contract

Router-level prefix and tags

const planetContract = oc
  .prefix('/planets')
  .tag('planets')
  .router({
    list: oc.input(z.object({ limit: z.number().optional() })),
    find: oc.input(z.object({ id: z.number() })),
  })

Type utilities

import type {
  InferContractRouterInputs,
  InferContractRouterOutputs,
} from '@orpc/contract'

type Inputs = InferContractRouterInputs<typeof contract>
type Outputs = InferContractRouterOutputs<typeof contract>

Build docs developers (and LLMs) love