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.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.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.