GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • oRPC
  • Documentation
  • Blog
  • Comparison
  • From V1
  • Sponsors
  • Experts
  • Releases
  • Discussions
  • LLM Context
  • LLM Context (Full)
  • V1 Documentation
  • Contract-First
  • Requirements

This view

  • Original page
  • New address

GeezerVison is showing orpc.dev — Middleware - oRPC

On this page
  • Overview
  • Initial Context
  • Metadata
  • Typesafe Errors
  • Middleware Context
  • Middleware Input
  • Middleware Output
  • Middleware Signal
  • Inline Middleware
  • Combining Middleware

Middleware

Run code before and after oRPC handlers with composable middleware that can inject context, guard access, and modify input or output.

Sponsors

ScreenshotOne.comThe screenshot API for developersYuzuWe're hiring NYC based engineersMisskeyHQDecentralized microblogging SNS born on Earth

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: DecoratedMiddleware<{
    something?: string;
} & object, {
    user: {
        id: number;
        name: string;
    };
}, unknown, any, {
    RATE_LIMITED: {};
}>
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']<{
    RATE_LIMITED: {};
}>(errors: {
    RATE_LIMITED: {};
}): Builder<{
    something?: string;
} & object, {
    RATE_LIMITED: {};
}>
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 RATE_LIMITED: {}RATE_LIMITED: {} }) // <- attach errors
.
Builder<{ something?: string; } & object, { RATE_LIMITED: {}; }>['middleware']<{
    user: {
        id: number;
        name: string;
    };
}, unknown, any>(middleware: Middleware<{
    something?: string;
} & object, {
    user: {
        id: number;
        name: string;
    };
}, unknown, any, {
    RATE_LIMITED: {};
}>): DecoratedMiddleware<{
    something?: string;
} & object, {
    user: {
        id: number;
        name: string;
    };
}, unknown, any, {
    RATE_LIMITED: {};
}>
Creates a standalone middleware that can be composed and applied to any compatible builder or procedure with `.use`.
@see{@link https://orpc.dev/docs/middleware Middleware}
middleware
(async ({
context: {
    something?: string;
} & object
context
, next: MiddlewareNext<any>
Invoke to continue the middleware chain.
next
,
errors: ORPCErrorConstructorMap<{
    RATE_LIMITED: {};
}>
errors
}) => {
// <- middleware logic try { // `await` is required to catch async errors return await
next: MiddlewareNext
<{
    user: {
        id: number;
        name: string;
    };
}>(options: MiddlewareNextOptions<{
    user: {
        id: number;
        name: string;
    };
}>) => MiddlewareResult<{
    user: {
        id: number;
        name: string;
    };
}, any>
Invoke to continue the middleware chain.
next
({
context: {
    user: {
        id: number;
        name: string;
    };
}
context
: {
// <- Inject additional context
user: {
    id: number;
    name: string;
}
user
: { id: numberid: 1, name: stringname: 'John' },
}, }) } catch (function (local var) error: unknownerror) { var console: Consoleconsole.Console.error(...data: any[]): void
The **`console.error()`** static method outputs a message to the console at the "error" log level. The message is only displayed to the user if the console is configured to display error output. In most cases, the log level is configured within the console UI. The message may be formatted as an error, with red colors and call stack information. [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/error_static)
error
(function (local var) error: unknownerror)
throw function (local var) error: unknownerror } finally { // Cleanup logic after execution } })

Initial Context

Use .$context to declare the initial context required when middleware is applied. Learn more in the Context Documentation.

Metadata

Use .meta to attach metadata. Every procedure that uses the middleware gets it. For example, an auth middleware can add its OpenAPI security requirement:

const requireAuth = os
  .meta(
    openapi({
      spec: (current) => ({ ...current, security: [{ bearerAuth: [] }] }),
    }),
  )
  .middleware(async ({ next }) => {
    // check auth...
    return next()
  })

const getProfile = os
  .use(requireAuth) // operation gets `security: [{ bearerAuth: [] }]`
  .handler(() => ({}))

implement(contract) ignores middleware metadata. Put it in the contract instead.

Typesafe Errors

Use .errors to attach error definitions to middleware. These errors are available in the middleware and any procedures that use it. Learn more in the Typesafe Error Handling documentation.

Middleware Context

Middleware can be used to inject or guard the context.

const 
const setting: DecoratedProcedure<DefaultInitialContext & object, DistributiveOmit<{
    auth: {
        userId: number;
    } | null;
}, "auth"> & {
    auth: {
        userId: number;
    };
}, InitialInputSchema, Schema<void>, Record<never, never>>
setting
= 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>>['use']<{
    auth: {
        userId: number;
    } | null;
}, DefaultInitialContext & object, Record<never, never>>(middleware: Middleware<DefaultInitialContext & object, {
    auth: {
        userId: number;
    } | null;
}, unknown, unknown, Record<never, never>>): BuilderWithMiddlewares<DefaultInitialContext & object, {
    auth: {
        userId: number;
    } | null;
}, Record<never, never>>
Applies a middleware that runs before the handler of every procedure built from this builder.
@see{@link https://orpc.dev/docs/middleware Middleware}
use
(async ({ context: DefaultInitialContext & objectcontext, next: MiddlewareNext<unknown>
Invoke to continue the middleware chain.
next
}) => {
return
next: MiddlewareNext
<{
    auth: {
        userId: number;
    } | null;
}>(options: MiddlewareNextOptions<{
    auth: {
        userId: number;
    } | null;
}>) => MiddlewareResult<{
    auth: {
        userId: number;
    } | null;
}, unknown>
Invoke to continue the middleware chain.
next
({
context: {
    auth: {
        userId: number;
    } | null;
}
context
: {
auth: {
    userId: number;
} | null
auth
: await
function auth(): {
    userId: number;
} | null
auth
(), // <- inject auth
}, }) }) .
BuilderWithMiddlewares<DefaultInitialContext & object, { auth: { userId: number; } | null; }, Record<never, never>>['use']<{
    auth: {
        userId: number;
    };
}, DistributiveOmit<DefaultInitialContext & object, "auth"> & {
    auth: {
        userId: number;
    } | null;
}, Record<never, never>>(middleware: Middleware<DistributiveOmit<DefaultInitialContext & object, "auth"> & {
    auth: {
        userId: number;
    } | null;
}, {
    auth: {
        userId: number;
    };
}, unknown, unknown, Record<never, never>>): BuilderWithMiddlewares<DefaultInitialContext & object, DistributiveOmit<{
    auth: {
        userId: number;
    } | null;
}, "auth"> & {
    auth: {
        userId: number;
    };
}, Record<...>>
Applies a middleware that runs before the handler of every procedure built from this builder.
@see{@link https://orpc.dev/docs/middleware Middleware}
use
(async ({
context: DistributiveOmit<DefaultInitialContext & object, "auth"> & {
    auth: {
        userId: number;
    } | null;
}
context
, next: MiddlewareNext<unknown>
Invoke to continue the middleware chain.
next
}) => {
if (!
context: DistributiveOmit<DefaultInitialContext & object, "auth"> & {
    auth: {
        userId: number;
    } | null;
}
context
.
auth: {
    userId: number;
} | null
auth
) {
// <- guard auth throw new new ORPCError<"UNAUTHORIZED", unknown>(code: "UNAUTHORIZED", options?: ORPCErrorOptions<unknown> | undefined): ORPCError<"UNAUTHORIZED", unknown>
Typed error carrying a `code`, a `message`, and optional `data`. Throw it from handlers or middleware to produce typed error responses on the client.
@see{@link https://orpc.dev/docs/error-handling#orpcerror-class Error Handling - ORPCError Class}
ORPCError
('UNAUTHORIZED')
} return
next: MiddlewareNext
<{
    auth: {
        userId: number;
    };
}>(options: MiddlewareNextOptions<{
    auth: {
        userId: number;
    };
}>) => MiddlewareResult<{
    auth: {
        userId: number;
    };
}, unknown>
Invoke to continue the middleware chain.
next
({
context: {
    auth: {
        userId: number;
    };
}
context
: {
auth: {
    userId: number;
}
auth
:
context: DistributiveOmit<DefaultInitialContext & object, "auth"> & {
    auth: {
        userId: number;
    } | null;
}
context
.
auth: {
    userId: number;
}
auth
, // <- override auth (now guaranteed to be non-null)
}, }) }) .
BuilderWithMiddlewares<DefaultInitialContext & object, DistributiveOmit<{ auth: { userId: number; } | null; }, "auth"> & { auth: { userId: number; }; }, Record<...>>['handler']<void>(handler: ProcedureHandler<DistributiveOmit<DefaultInitialContext & object, "auth"> & DistributiveOmit<{
    auth: {
        userId: number;
    } | null;
}, "auth"> & {
    auth: {
        userId: number;
    };
}, unknown, void, ORPCErrorConstructorMap<Record<never, never>>>): DecoratedProcedure<DefaultInitialContext & object, DistributiveOmit<{
    auth: {
        userId: number;
    } | null;
}, "auth"> & {
    auth: {
        userId: number;
    };
}, InitialInputSchema, Schema<...>, Record<...>>
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 ({
context: DistributiveOmit<DefaultInitialContext & object, "auth"> & DistributiveOmit<{
    auth: {
        userId: number;
    } | null;
}, "auth"> & {
    auth: {
        userId: number;
    };
}
context
}) => {
var console: Consoleconsole.Console.log(...data: any[]): void
The **`console.log()`** static method outputs a message to the console. [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/log_static)
log
(
context: DistributiveOmit<DefaultInitialContext & object, "auth"> & DistributiveOmit<{
    auth: {
        userId: number;
    } | null;
}, "auth"> & {
    auth: {
        userId: number;
    };
}
context
.
auth: {
    userId: number;
}
auth
) // <- auth is guaranteed to be non-null here
})

Context passed to next must not conflict with the existing context; it is merged at runtime.

Middleware Input

Middleware can access input in type-safe manner, enabling use cases like permission checks.

const canUpdate = os.middleware(async ({ context, next }, input: number) => {
  // Perform permission check
  return next()
})

const ping = os
  .input(z.number())
  .use(canUpdate) // <- input already matches middleware's expected shape
  .handler(async ({ input }) => {
    // Handler logic
  })

const pong = os
  .input(z.object({ id: z.number() }))
  .use(canUpdate.adaptInput((input) => input.id)) // <- adapt input to match middleware's expected shape
  .handler(async ({ input }) => {
    // Handler logic
  })

You can adapt a middleware to accept a different input shape by using .adaptInput.

const canUpdate = os.middleware(async ({ context, next }, input: number) => {
  return next()
})

// Transform middleware to accept a new input shape
const adaptedCanUpdate = canUpdate.adaptInput((input: { id: number }) => input.id)

A middleware placed between multiple input schemas also receives the raw fields the remaining schemas have not validated yet. Only trust fields validated before the middleware; treat the rest as untrusted client input.

Sponsors

ScreenshotOne.comThe screenshot API for developersYuzuWe're hiring NYC based engineers

Middleware Output

Middleware can also modify the output of a handler, such as implementing caching mechanisms.

const cache = os.middleware(async ({ context, next, path }, input, done) => {
  const cacheKey = path.join('/') + JSON.stringify(input)

  if (db.has(cacheKey)) {
    return done({ output: db.get(cacheKey) })
  }

  const result = await next({})

  db.set(cacheKey, result.output)

  return result
})

Middleware Signal

Pass signal to next to replace the signal for downstream middleware and the handler, such as aborting them after a timeout.

const timeout = os.middleware(async ({ signal, next }) => {
  const timeoutSignal = AbortSignal.timeout(5000)

  return next({
    signal: signal ? AbortSignal.any([signal, timeoutSignal]) : timeoutSignal, // <- received by downstream middleware and the handler
  })
})

Inline Middleware

Middleware is simply a function that can be defined inline with .use, which is useful for simple middleware cases.

const example = os
  .use(async ({ context, next }) => {
    // Execute logic before the handler
    return next()
  })
  .handler(async ({ context }) => {
    // Handler logic
  })

Combining Middleware

Multiple middleware functions can be combined using .use.

const mergedMiddleware = aMiddleware.use(async ({ next }) => next()).use(anotherMiddleware)

To concatenate two middlewares with different input types, use .adaptInput to align their inputs first.

Last updated on November 9, 2026

Was this page helpful?

Thanks for your feedback!

You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.