Build, serve, and call end-to-end typesafe APIs with oRPC v2. Use for any task in a project that depends on `@orpc/*` packages, even a one-procedure change: defining procedures with the os builder (.input/.output/.handler, any Standard Schema validator), assembling routers, middleware and context, typesafe errors with ORPCError, serving via RPCHandler on any runtime adapter, calling from server-side clients (call, createRouterClient) or client-side clients (createORPCClient with RPCLink), integrating TanStack Query, or streaming over SSE. Pretrained oRPC knowledge describes v1 and is wrong for v2, so load this skill even when the change looks trivial. Biases toward retrieval from the oRPC docs over pre-trained knowledge. For REST/OpenAPI exposure, prefer the orpc-openapi skill; for contract-first design, the orpc-contract skill; for tRPC or oRPC v1 migrations, the orpc-migrate skill.
Build, serve, and call end-to-end typesafe APIs with oRPC v2. Use for any task in a project that depends on `@orpc/*` packages, even a one-procedure change: defining procedures with the os builder (.input/.output/.handler, any Standard Schema validator), assembling routers, middleware and context, typesafe errors with ORPCError, serving via RPCHandler on any runtime adapter, calling from server-side clients (call, createRouterClient) or client-side clients (createORPCClient with RPCLink), integrating TanStack Query, or streaming over SSE. Pretrained oRPC knowledge describes v1 and is wrong for v2, so load this skill even when the change looks trivial. Biases toward retrieval from the oRPC docs over pre-trained knowledge. For REST/OpenAPI exposure, prefer the orpc-openapi skill; for contract-first design, the orpc-contract skill; for tRPC or oRPC v1 migrations, the orpc-migrate skill.
license
MIT
oRPC
oRPC is a typesafe API framework: write plain TypeScript functions on the server, call them from clients like local functions. Input is validated at runtime, types flow end to end, and there is no code generation step. The same router can also be served as a REST API with an OpenAPI spec.
This skill targets oRPC v2. Check what is installed before writing code: npm ls @orpc/server (or any @orpc/* package). A 1.x version means v1, where this skill's guidance does not apply; use the orpc-migrate skill to upgrade. v2 currently ships under the beta dist-tag (npm install @orpc/server@beta @orpc/client@beta; a plain install silently gets v1). If npm view @orpc/server dist-tags shows latest at 2.x, the beta has ended: install normally and read docs at https://orpc.dev instead of .
Pretrained oRPC knowledge describes v1 and is often wrong for v2 (routing moved to .meta(openapi(...)), RPCLink split url into origin plus a path, automatic middleware dedupe was removed). Prefer retrieval: the index of every docs page is at https://v2.orpc.dev/llms.txt; see Full documentation for the mechanics.
Package map:
@orpc/server: the os builder, routers, middleware, RPCHandler, server-side clients (call, createRouterClient), implement for mocks
Integration packages such as @orpc/tanstack-query and @orpc/nest
Define procedures
Build a procedure with the os builder: describe input with a schema, implement with .handler. Zod, Valibot, ArkType, and any other Standard Schema library work for .input, .output, and error data.
import { os } from'@orpc/server'import * as z from'zod'exportconst listPlanets = os
.handler(async () => [{ id: 1, name: 'Earth' }]) // no .input: takes no argumentsexportconst findPlanet = os
.input(z.object({ id: z.number() }))
.handler(async ({ input }) => ({ id: input.id, name: 'Earth' }))
.handler is the only required step. The full chain, every other step optional:
const example = os
.$context<{ headers: Headers }>() // initial context this procedure requires
.errors({ NOT_FOUND: {} }) // typed errors
.use(requireAuth) // middleware
.input(z.object({ id: z.number() }))
.output(z.object({ id: z.number(), name: z.string() })) // optional; also speeds up type checking
.handler(async ({ input, context, errors }) => ({ id: input.id, name: 'Earth' }))
Every builder step returns a new instance, so share base builders freely: const authed = os.use(requireAuth) then build many procedures from authed.
Assemble a router
A router is a plain object mapping keys to procedures (or nested routers). Do not use the keys then, bind, valueOf, toString, toJSON.
exportconst router = {
planet: { list: listPlanets, find: findPlanet },
admin: os.use(requireAuth).router({ deletePlanet }), // apply shared middleware to a subtreeplanetLazy: os.lazy(() =>import('./planet')), // code-split; module's default export is a router
}
Infer types with InferRouterInputs / InferRouterOutputs from @orpc/server. Applying .use at both router and procedure level can run the same middleware twice; see the dedupe pattern below.
Middleware and context
Context comes from two places. Initial context is declared with .$context and passed explicitly when serving or calling (environment values: headers, env, db). Injected context is added at runtime by middleware via next({ context }) (runtime values: the authenticated user).
import { ORPCError, os } from'@orpc/server'const base = os.$context<{ headers: Headers }>()
const requireAuth = base.middleware(async ({ context, next }) => {
const user = awaitparseUser(context.headers)
if (!user) {
thrownewORPCError('UNAUTHORIZED')
}
returnnext({ context: { user } }) // handler now sees context.user, typed non-null
})
.use accepts named middleware or inline functions. Middleware registered before .input runs before validation, the rest after. Middleware can also declare typed input (os.middleware(async ({ next }, id: number) => ...)); adapt mismatched shapes with .use(mw.adaptInput(input => input.id)).
Best practice, dedupe expensive middleware: the same middleware can run twice in one call (router-level plus procedure-level .use, or a procedure calling another). Cache the result in context:
Throw ORPCError (a code plus optional message and data). Both message and data are sent to the client, so never put secrets in them. Throw only Error instances, never literals.
Define errors with .errors so clients can infer each error's shape:
throw new ORPCError('NOT_FOUND') inside that handler is converted to the matching typed error when code and data match. Convert custom error classes to ORPCError in a middleware try/catch.
Serve with RPCHandler
RPCHandler matches requests to procedures, validates input, runs handlers, and encodes results. Pick the adapter for your runtime. Fetch API (Bun, Deno, Cloudflare Workers):
Unmatched requests fall through to your own handling. By default RPCHandler accepts only POST, PUT, PATCH, and DELETE; enabling GET via allowMethods is a CSRF risk with cookie auth, see RPC Handler. Handler options also include interceptors, routingInterceptors, clientInterceptors, plugins, filter, and errorStatusMap. Adapters also exist for AWS Lambda, Fastify, WebSocket, Message Port, and React Native.
Call procedures
Server side (same process, no HTTP; also the fastest way to test procedures):
Streaming / SSE: return an async generator from .handler, validate events with asyncIteratorObject, resume via lastEventId: AsyncIteratorObject
OpenAPI: serve the same router as REST with routing metadata plus OpenAPIHandler, and generate a spec (covered in depth by the orpc-openapi skill): OpenAPI Handler
Contract-first: define contracts with @orpc/contract, implement with implement (covered in depth by the orpc-contract skill): Contracts
Plugins for handler and link: batch, CORS, dedupe, retry, compression, request limits, smart coercion, static files, timeout, tmp file upload, and more. Fetch a plugin's docs page before configuring it; option names are not guessable: Plugins
Testing: call procedures directly; mock with implement(router.planet.list).handler(() => []), and run the project's typecheck before declaring success, since end-to-end types are oRPC's first correctness signal: Testing and Mocking
Migrating from tRPC or oRPC v1: use the orpc-migrate skill
Full documentation
This skill is an overview; fetch exact docs instead of guessing APIs, and if this skill and a fetched page disagree, trust the page. While v2 is in beta the docs are served at https://v2.orpc.dev:
https://v2.orpc.dev/llms.txt : index of every page with descriptions (links inside print the orpc.dev domain; swap in v2.orpc.dev before fetching)