Skip to content
You are reading the v2 docs, currently in beta.V1 docs
oRPC
Esc
↑↓navigate↵open⌘Jpreview
On this page

Rate Limit Helpers

Add rate limiting to oRPC with a unified RateLimiter interface, storage adapters, procedure middleware, and a handler plugin for HTTP headers.

Installation

npm install @orpc/ratelimit@beta
pnpm add @orpc/ratelimit@beta
yarn add @orpc/ratelimit@beta
bun add @orpc/ratelimit@beta

Basic Usage

The core concept is the RateLimiter interface, which defines a standard way to check and enforce rate limits. You can create your own custom limiter or use one of the provided adapters:

The limit method accepts a key and an optional weight value, which defaults to 1, so a single request can consume multiple points.

import { class ORPCError<TCode extends ORPCErrorCode, TData>
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
} from '@orpc/server'
const const limiter: MemoryRateLimiterlimiter = new new MemoryRateLimiter(options: MemoryRateLimiterOptions): MemoryRateLimiter
Rate limiter adapter backed by in-memory storage. Enforces a fixed-window limit, with optional blocking mode.
@see{@link https://orpc.dev/docs/helpers/ratelimit#adapters Rate Limit Helpers - Adapters}
MemoryRateLimiter
({
MemoryRateLimiterOptions.maxRequests: number
Maximum number of requests allowed within the window.
maxRequests
: 5,
MemoryRateLimiterOptions.window: number
The duration of the fixed window in milliseconds.
window
: 60000,
}) const const result: Required<RateLimitResult>result = await const limiter: MemoryRateLimiterlimiter.MemoryRateLimiter.limit(key: string, options?: RateLimitOptions): Promise<Required<RateLimitResult>>limit('user:123', { RateLimitOptions.weight?: number | undefined
The weight of the request. Determines how many tokens or quota units are consumed by this request. Must be an integer greater than 0.
@default1
weight
: 2 })
if (!const result: Required<RateLimitResult>result.success: boolean
Whether the request may pass(true) or exceeded the limit(false)
success
) {
throw new
new ORPCError<"TOO_MANY_REQUESTS", {
    limit: number;
    remaining: number;
    reset: number;
}>(code: "TOO_MANY_REQUESTS", options: ORPCErrorOptions<{
    limit: number;
    remaining: number;
    reset: number;
}>): ORPCError<"TOO_MANY_REQUESTS", {
    limit: number;
    remaining: number;
    reset: number;
}>
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
('TOO_MANY_REQUESTS', {
data: {
    limit: number;
    remaining: number;
    reset: number;
}
data
: {
limit: numberlimit: const result: Required<RateLimitResult>result.limit: number
Maximum number of requests allowed within a window.
limit
,
remaining: numberremaining: const result: Required<RateLimitResult>result.remaining: number
How many requests the user has left within the current window.
remaining
,
reset: numberreset: const result: Required<RateLimitResult>result.reset: number
Unix timestamp in milliseconds when the limits are reset.
reset
,
}, }) }

Blocking Mode

Some adapters support blocking mode, which waits until capacity becomes available instead of rejecting requests immediately.

const limiter = new MemoryRateLimiter({
  maxRequests: 10,
  window: 60000,
  blockingUntilReady: {
    enabled: true, // Disabled by default
    timeout: 5000, // Wait up to 5 seconds
  },
})

Ratelimit Middleware

The ratelimit helper creates middleware that enforces rate limits for procedures.

import { ratelimit, RateLimiter } from '@orpc/ratelimit'

const procedure = os
  .$context<{ ratelimiter: RateLimiter }>()
  .input(z.object({ email: z.email() }))
  .use(
    ratelimit({
      limiter: ({ context }) => context.ratelimiter,
      key: ({ context }, input) => `login:${input.email}`,
      weight: 1, // Optional weight for each request, default is 1
    }),
  )
  .handler(({ input }) => {
    return { success: true }
  })

const ratelimiter = new MemoryRateLimiter({
  maxRequests: 10,
  window: 60000,
})

const result = await call(
  procedure,
  { email: 'user@example.com' },
  { context: { ratelimiter } }
)

Handler Plugin

The RateLimitHandlerPlugin automatically adds HTTP rate limiting headers (RateLimit-* and Retry-After) to responses when used with Ratelimit Middleware. This lets clients inspect the current limit state and know when they can retry after hitting a limit.

import { RateLimitHandlerPlugin } from '@orpc/ratelimit'

const handler = new RPCHandler(router, {
  plugins: [
    new RateLimitHandlerPlugin(),
  ],
})

Adapters

Memory

Keeps counters in the memory of the current process, so limits are not shared between instances. A good fit for development, tests, and single-process servers.

import { MemoryRateLimiter } from '@orpc/ratelimit/memory'

const limiter = new MemoryRateLimiter({
  /**
   * Maximum number of requests allowed within the window.
   */
  maxRequests: 10,

  /**
   * The duration of the fixed window in milliseconds.
   */
  window: 60000,

  blockingUntilReady: {
    /**
     * Block until the request may pass or timeout is reached.
     *
     * @default false
     */
    enabled: false,

    /**
     * milliseconds
     */
    timeout: 5000
  },
})

Redis

Stores counters in Redis, so every instance using the same server enforces the same limits.

import { RedisRateLimiter } from '@orpc/ratelimit/redis'
import { createClient } from 'redis'

const client = createClient({ url: 'redis://localhost:6379' })

// RedisRateLimiter lazily connects to Redis when needed.
// You can still call `client.connect()` manually, but it is optional.
await client.connect()

const limiter = new RedisRateLimiter(client, {
  /**
   * The prefix to use for Redis keys.
   *
   * @default ''
   */
  prefix: '',

  /**
   * Maximum number of requests allowed within the window.
   */
  maxRequests: 10,

  /**
   * The duration of the fixed window in milliseconds.
   */
  window: 60000,

  blockingUntilReady: {
    /**
     * Block until the request may pass or timeout is reached.
     *
     * @default false
     */
    enabled: false,

    /**
     * milliseconds
     */
    timeout: 5000
  },
})

Upstash

Delegates to an @upstash/ratelimit instance, so the algorithm and limits are configured there. A good fit for serverless and edge runtimes.

import { Ratelimit } from '@upstash/ratelimit'
import { Redis } from '@upstash/redis'
import { UpstashRateLimiter } from '@orpc/ratelimit/upstash'

const redis = Redis.fromEnv()
const ratelimit = new Ratelimit({
  redis,
  limiter: Ratelimit.slidingWindow(10, '60 s'),
  prefix: 'orpc:', // Optional key prefix
})

const limiter = new UpstashRateLimiter(ratelimit, {
  blockingUntilReady: {
    /**
     * Block until the request may pass or timeout is reached.
     *
     * @default false
     */
    enabled: false,

    /**
     * milliseconds
     */
    timeout: 5000
  },

  /**
   * For the MultiRegion setup we do some synchronizing in the background, after returning the current limit.
   * Or when analytics is enabled, we send the analytics asynchronously after returning the limit.
   * In most case you can simply ignore this.
   *
   * On Vercel Edge or Cloudflare workers, you might need `.bind` before assign:
   * ```ts
   * const ratelimiter = new UpstashRateLimiter(ratelimit, {
   *   waitUntil: ctx.waitUntil.bind(ctx),
   * })
   * ```
   */
  waitUntil: undefined
})

Bun Redis

The Redis adapter for Bun’s built-in Redis client, with no extra dependency. It shares counters with RedisRateLimiter.

import { BunRedisRateLimiter } from '@orpc/bun'
import { redis } from 'bun'

const limiter = new BunRedisRateLimiter(redis, {
  /**
   * The prefix to use for Redis keys.
   *
   * @default ''
   */
  prefix: '',

  /**
   * Maximum number of requests allowed within the window.
   */
  maxRequests: 10,

  /**
   * The duration of the fixed window in milliseconds.
   */
  window: 60000,

  blockingUntilReady: {
    /**
     * Block until the request may pass or timeout is reached.
     *
     * @default false
     */
    enabled: false,

    /**
     * milliseconds
     */
    timeout: 5000
  },
})

Cloudflare

Uses the Workers Rate Limiting binding, so limits are configured in your Worker settings rather than in the adapter.

import { CloudflareRateLimiter } from '@orpc/cloudflare'

export default {
  async fetch(request, env) {
    const limiter = new CloudflareRateLimiter(env.MY_RATE_LIMITER, {
      /**
       * The prefix to use for cloudflare ratelimit.
       *
       * @default ''
       */
      prefix: ''
    })
  }
}

Last updated on September 25, 2026

Was this page helpful?