Skip to content
oRPC
Esc
navigateopen⌘Jpreview
On this page

Rate Limit Helpers

Rate Limit helpers provide a unified set of adapters, middleware, and handler plugins for adding rate limiting to oRPC applications. They are flexible and composable, so you can use different rate-limiting strategies and storage backends without changing your procedure code.

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 for popular storage backends. 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
,
}, }) }

Adapters

The package includes adapters for multiple storage backends and runtimes. Each adapter might require maxRequests and window to configure the limit, along with adapter specific options.

Name Blocking Mode Adapter for
MemoryRateLimiter In-memory storage
RedisRateLimiter Redis
UpstashRateLimiter Upstash Rate Limit
BunRedisRateLimiter Bun’s Redis
CloudflareRateLimiter Cloudflare’s Rate Limiting
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
  },
})
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
  },
})
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
})
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
  },
})
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: ''
    })
  }
}

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(),
  ],
})

Last updated on August 6, 2026

Was this page helpful?