Skip to content
oRPC
Esc
navigateopen⌘Jpreview
On this page

Procedure

Procedures are the core building blocks of oRPC. They define the logic for handling specific operations, including input validation, output validation, and middleware application. Each procedure is created using a builder pattern that allows for flexible composition and reuse.

Overview

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 example: DecoratedProcedure<{
    something?: string;
} & object, {
    user: {
        id: number;
    };
}, z.ZodObject<{
    id: z.ZodNumber;
    name: z.ZodString;
}, z.core.$strip>, z.ZodObject<{
    id: z.ZodNumber;
    name: z.ZodString;
}, z.core.$strip>, {
    NOT_FOUND: {};
}, never>
example
= 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>>.$context<{
    something?: string;
}>(): Builder<{
    something?: string;
} & object, Record<never, never>>
$context
<{ something?: string | undefinedsomething?: string }>() // <- define initial context
.
Builder<{ something?: string; } & object, Record<never, never>>.meta(...plugins: MetaPlugin<InitialInputSchema, InitialOutputSchema, Record<never, never>>[]): Builder<{
    something?: string;
} & object, Record<never, never>>
meta
(const someMeta: AnyMetaPluginsomeMeta) // <- attach metadata
.
Builder<{ something?: string; } & object, Record<never, never>>.errors<{
    NOT_FOUND: {};
}>(errors: {
    NOT_FOUND: {};
}): Builder<{
    something?: string;
} & object, {
    NOT_FOUND: {};
}>
errors
({ type NOT_FOUND: {}NOT_FOUND: {} }) // <- define errors
.
Builder<{ something?: string; } & object, { NOT_FOUND: {}; }>.use<{
    user: {
        id: number;
    };
}, DefaultInitialContext & object, Record<never, never>>(middleware: Middleware<(DefaultInitialContext & object) | ({
    something?: string;
} & object), {
    user: {
        id: number;
    };
}, unknown, unknown, Record<never, never>>): BuilderWithMiddlewares<{
    something?: string;
} & object, {
    user: {
        id: number;
    };
}, {
    NOT_FOUND: {};
}>
use
(
const requireAuth: DecoratedMiddleware<DefaultInitialContext & object, {
    user: {
        id: number;
    };
}, unknown, any, Record<never, never>>
requireAuth
) // <- apply middleware
.
BuilderWithMiddlewares<{ something?: string; } & object, { user: { id: number; }; }, { NOT_FOUND: {}; }>['input']<z.ZodObject<{
    id: z.ZodNumber;
    name: z.ZodString;
}, z.core.$strip>>(schema: z.ZodObject<{
    id: z.ZodNumber;
    name: z.ZodString;
}, z.core.$strip>): BuilderWithInput<{
    something?: string;
} & object, {
    user: {
        id: number;
    };
}, z.ZodObject<{
    id: z.ZodNumber;
    name: z.ZodString;
}, z.core.$strip>, {
    NOT_FOUND: {};
}>
input
(import zz.
function object<{
    id: z.ZodNumber;
    name: z.ZodString;
}>(shape?: {
    id: z.ZodNumber;
    name: z.ZodString;
} | undefined, params?: string | {
    error?: string | z.core.$ZodErrorMap<NonNullable<z.core.$ZodIssueInvalidType<unknown> | z.core.$ZodIssueUnrecognizedKeys>> | undefined;
    message?: string | undefined | undefined;
} | undefined): z.ZodObject<{
    id: z.ZodNumber;
    name: z.ZodString;
}, z.core.$strip>
object
({ id: z.ZodNumberid: import zz.function number(params?: string | z.core.$ZodNumberParams): z.ZodNumbernumber(), name: z.ZodStringname: import zz.function string(params?: string | z.core.$ZodStringParams): z.ZodString (+1 overload)string() })) // <- input validation
.
BuilderWithInput<{ something?: string; } & object, { user: { id: number; }; }, ZodObject<{ id: ZodNumber; name: ZodString; }, $strip>, { NOT_FOUND: {}; }>['use']<object, {
    user: {
        id: number;
    };
} & object, Record<never, never>>(middleware: Middleware<(Omit<{
    something?: string;
} & object, "user"> & {
    user: {
        id: number;
    };
}) | ({
    user: {
        id: number;
    };
} & object), object, {
    id: number;
    name: string;
}, unknown, Record<never, never>>): BuilderWithInput<{
    something?: string;
} & object, {
    user: {
        id: number;
    };
}, z.ZodObject<{
    id: z.ZodNumber;
    name: z.ZodString;
}, z.core.$strip>, {
    NOT_FOUND: {};
}>
use
(
const canEdit: DecoratedMiddleware<{
    user: {
        id: number;
    };
} & object, object, number, any, Record<never, never>>
canEdit
.
DecoratedMiddleware<{ user: { id: number; }; } & object, object, number, any, Record<never, never>>.adaptInput<{
    id: number;
    name: string;
}>(adapt: (input: {
    id: number;
    name: string;
}) => number): DecoratedMiddleware<{
    user: {
        id: number;
    };
} & object, object, {
    id: number;
    name: string;
}, any, Record<never, never>>
adaptInput
(
input: {
    id: number;
    name: string;
}
input
=>
input: {
    id: number;
    name: string;
}
input
.id: numberid)) // <- middleware with typed input
.
BuilderWithInput<{ something?: string; } & object, { user: { id: number; }; }, ZodObject<{ id: ZodNumber; name: ZodString; }, $strip>, { NOT_FOUND: {}; }>['output']<z.ZodObject<{
    id: z.ZodNumber;
    name: z.ZodString;
}, z.core.$strip>>(schema: z.ZodObject<{
    id: z.ZodNumber;
    name: z.ZodString;
}, z.core.$strip>): BuilderWithInputOutput<{
    something?: string;
} & object, {
    user: {
        id: number;
    };
}, z.ZodObject<{
    id: z.ZodNumber;
    name: z.ZodString;
}, z.core.$strip>, z.ZodObject<{
    id: z.ZodNumber;
    name: z.ZodString;
}, z.core.$strip>, {
    NOT_FOUND: {};
}>
output
(import zz.
function object<{
    id: z.ZodNumber;
    name: z.ZodString;
}>(shape?: {
    id: z.ZodNumber;
    name: z.ZodString;
} | undefined, params?: string | {
    error?: string | z.core.$ZodErrorMap<NonNullable<z.core.$ZodIssueInvalidType<unknown> | z.core.$ZodIssueUnrecognizedKeys>> | undefined;
    message?: string | undefined | undefined;
} | undefined): z.ZodObject<{
    id: z.ZodNumber;
    name: z.ZodString;
}, z.core.$strip>
object
({ id: z.ZodNumberid: import zz.function number(params?: string | z.core.$ZodNumberParams): z.ZodNumbernumber(), name: z.ZodStringname: import zz.function string(params?: string | z.core.$ZodStringParams): z.ZodString (+1 overload)string() })) // <- output validation
.
BuilderWithInputOutput<{ something?: string; } & object, { user: { id: number; }; }, ZodObject<{ id: ZodNumber; name: ZodString; }, $strip>, ZodObject<{ id: ZodNumber; name: ZodString; }, $strip>, { NOT_FOUND: {}; }>['handler']<{
    id: number;
    name: "example";
}>(handler: ProcedureHandler<Omit<{
    something?: string;
} & object, "user"> & {
    user: {
        id: number;
    };
}, {
    id: number;
    name: string;
}, {
    id: number;
    name: "example";
}, ORPCErrorConstructorMap<{
    NOT_FOUND: {};
}>>): DecoratedProcedure<{
    something?: string;
} & object, {
    user: {
        id: number;
    };
}, z.ZodObject<{
    id: z.ZodNumber;
    name: z.ZodString;
}, z.core.$strip>, z.ZodObject<{
    id: z.ZodNumber;
    name: z.ZodString;
}, z.core.$strip>, {
    NOT_FOUND: {};
}, never>
handler
(async ({
input: {
    id: number;
    name: string;
}
input
,
context: Omit<{
    something?: string;
} & object, "user"> & {
    user: {
        id: number;
    };
}
context
,
errors: ORPCErrorConstructorMap<{
    NOT_FOUND: {};
}>
errors
}) => { // <- handler logic
return { id: numberid: 1, name: "example"name: 'example' } })

Initial Context

Use .$context to declare the initial context required for a procedure to execute. Learn more in the Context Documentation.

Metadata

Use .meta to attach metadata to a procedure. You can access this metadata later in middleware or plugins. Learn more in the Metadata Documentation.

Typesafe Errors

Use .errors to define error definitions for a procedure. These errors can be thrown in the handler or middleware and will be properly typed on the client. Learn more in the Typesafe Error Handling documentation.

Input/Output Validation

oRPC supports Zod, Valibot, Arktype, and any other Standard Schema library for validation.

Multiple Schemas

.input and .output can be called multiple times. Each call adds another schema instead of replacing an earlier one.

const example = os
  .input(z.looseObject({ name: z.string() }))
  .input(z.looseObject({ id: z.number() }))
  .output(z.looseObject({ name: z.string() }))
  .output(z.looseObject({ id: z.number() }))
  .handler(async ({ input }) => {
    return { id: 1, name: 'example' }
  })

type Utility

For simple use cases without external libraries, use oRPC’s built-in type utility. It takes a mapping function as its first argument:

import { type } from '@orpc/server'

const example = os
  .input(type<{ value: number }>())
  .output(type<{ value: number }, number>(({ value }) => value))
  .handler(async ({ input }) => input)

Using Middleware

The .use method allows you to pass middleware, which must call next to continue execution.

const aMiddleware = os.middleware(async ({ context, next }) => next())

const example = os
  .use(aMiddleware) // Apply middleware
  .use(async ({ context, next }) => next()) // Inline middleware
  .handler(async ({ context }) => { /* logic */ })

Reusability

Each modification to a builder creates a completely new instance, avoiding reference issues. This makes it easy to reuse and extend procedures efficiently.

const pub = os.use(logMiddleware) // Base setup for procedures that publish
const authed = pub.use(requireAuth) // Extends 'pub' with authentication

const pubExample = pub
  .handler(async ({ context }) => { /* logic */ })

const authedExample = authed
  .handler(async ({ context }) => { /* logic */ })

This pattern helps prevent duplication while maintaining flexibility.

Last updated on August 6, 2026

Was this page helpful?