Skip to content
oRPC
Esc
navigateopen⌘Jpreview
On this page

Procedure

Learn how oRPC procedures combine input and output validation, middleware, metadata, and typesafe errors through a composable builder.

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>>
Declares the initial context type that must be provided when executing procedures built from this builder.
@see{@link https://orpc.dev/docs/context#initial-context Context - Initial Context}
$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>>
Applies metadata plugins to procedures built from this builder.
@see{@link https://orpc.dev/docs/metadata Metadata}
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: {};
}>
Defines typesafe errors that procedures built from this builder can throw via the `errors` utility in handlers and middleware.
@see{@link https://orpc.dev/docs/error-handling#typesafe-errors Error Handling - Typesafe Errors}
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: {};
}>
Applies a middleware that runs before the handler of every procedure built from this builder.
@see{@link https://orpc.dev/docs/middleware Middleware}
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: {};
}>
Defines the input schema used to validate and type the procedure input.
@see{@link https://orpc.dev/docs/procedure#inputoutput-validation Procedure - Input/Output Validation}
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: {};
}>
Applies a middleware that runs before the handler and can access the validated input.
@see{@link https://orpc.dev/docs/middleware Middleware}
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: {};
}>
Defines the output schema used to validate and type the procedure output.
@see{@link https://orpc.dev/docs/procedure#inputoutput-validation Procedure - Input/Output Validation}
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>
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 ({
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 8, 2026

Was this page helpful?