diff --git a/apps/content/docs/helpers/ratelimit.mdx b/apps/content/docs/helpers/ratelimit.mdx
index 69738ed99..fbb163c45 100644
--- a/apps/content/docs/helpers/ratelimit.mdx
+++ b/apps/content/docs/helpers/ratelimit.mdx
@@ -13,7 +13,17 @@ npm install @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.
+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](#adapters):
+
+| Name | Blocking Mode | Adapter for |
+| -------------------------------------- | ------------- | --------------------------------------------------------------------------------------------------------- |
+| [`MemoryRateLimiter`](#memory) | ✅ | In-memory storage |
+| [`RedisRateLimiter`](#redis) | ✅ | [Redis](https://github.com/redis/redis) |
+| [`UpstashRateLimiter`](#upstash) | ✅ | [Upstash Rate Limit](https://www.npmjs.com/package/@upstash/ratelimit) |
+| [`BunRedisRateLimiter`](#bun-redis) | ✅ | [Bun's Redis](https://bun.com/docs/runtime/redis) |
+| [`CloudflareRateLimiter`](#cloudflare) | ❌ | [Cloudflare's Rate Limiting](https://developers.cloudflare.com/workers/runtime-apis/bindings/rate-limit/) |
+
+The `limit` method accepts a key and an optional `weight` value, which defaults to `1`, so a single request can consume multiple points.
```ts twoslash
import { MemoryRateLimiter } from '@orpc/ratelimit/memory'
@@ -38,22 +48,114 @@ if (!result.success) {
}
```
-## Adapters
+### Blocking Mode
+
+Some adapters support blocking mode, which waits until capacity becomes available instead of rejecting requests immediately.
+
+```ts
+const limiter = new MemoryRateLimiter({
+ maxRequests: 10,
+ window: 60000,
+ blockingUntilReady: {
+ enabled: true, // Disabled by default
+ timeout: 5000, // Wait up to 5 seconds
+ },
+})
+```
-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.
+## Ratelimit Middleware
-| Name | Blocking Mode | Adapter for |
-| ----------------------- | ------------- | --------------------------------------------------------------------------------------------------------- |
-| `MemoryRateLimiter` | ✅ | In-memory storage |
-| `RedisRateLimiter` | ✅ | [Redis](https://github.com/redis/redis) |
-| `UpstashRateLimiter` | ✅ | [Upstash Rate Limit](https://www.npmjs.com/package/@upstash/ratelimit) |
-| `BunRedisRateLimiter` | ✅ | [Bun's Redis](https://bun.com/docs/runtime/redis) |
-| `CloudflareRateLimiter` | ❌ | [Cloudflare's Rate Limiting](https://developers.cloudflare.com/workers/runtime-apis/bindings/rate-limit/) |
+The `ratelimit` helper creates middleware that enforces rate limits for [procedures](/docs/procedure).
-
+```ts
+import { ratelimit, RateLimiter } from '@orpc/ratelimit'
-```ts memory
+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 } }
+)
+```
+
+:::info[Automatic Deduplication]
+When the same `limiter` and `key` combination is used multiple times in a single request chain, the `ratelimit` middleware performs the rate limit check only once. This behavior follows the [Dedupe Middleware](/docs/recipes/dedupe-middleware) recipe. To disable deduplication, set `dedupe: false`.
+:::
+
+:::tip[Conditional Limiter]
+You can choose different limiters dynamically based on the request context:
+
+```ts
+const premiumLimiter = new MemoryRateLimiter({
+ maxRequests: 100,
+ window: 60000,
+})
+
+const standardLimiter = new MemoryRateLimiter({
+ maxRequests: 10,
+ window: 60000,
+})
+
+const result = await call(
+ procedure,
+ { email: 'user@example.com' },
+ {
+ context: {
+ ratelimiter: isPremiumUser ? premiumLimiter : standardLimiter,
+ },
+ },
+)
+```
+
+:::
+
+## Handler Plugin
+
+The `RateLimitHandlerPlugin` automatically adds HTTP rate limiting headers (`RateLimit-*` and `Retry-After`) to responses when used with [Ratelimit Middleware](#ratelimit-middleware). This lets clients inspect the current limit state and know when they can retry after hitting a limit.
+
+```ts
+import { RateLimitHandlerPlugin } from '@orpc/ratelimit'
+
+const handler = new RPCHandler(router, {
+ plugins: [
+ new RateLimitHandlerPlugin(),
+ ],
+})
+```
+
+:::info
+You can combine this plugin with [Retry After Plugin](/docs/plugins/retry-after) to enable automatic client-side retries based on server rate limiting headers.
+:::
+
+:::info
+The `handler` can be any supported oRPC handler, such as [RPCHandler](/docs/rpc/handler), [OpenAPIHandler](/docs/openapi/handler), or a custom one.
+:::
+
+## 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.
+
+```ts
import { MemoryRateLimiter } from '@orpc/ratelimit/memory'
const limiter = new MemoryRateLimiter({
@@ -83,10 +185,15 @@ const limiter = new MemoryRateLimiter({
})
```
-```ts redis
+### Redis
+
+Stores counters in Redis, so every instance using the same server enforces the same limits. Works with both standalone and cluster clients.
+
+```ts
import { RedisRateLimiter } from '@orpc/ratelimit/redis'
import { createClient } from 'redis'
+// Both standalone (`createClient`) and cluster (`createCluster`) clients are supported.
const client = createClient({ url: 'redis://localhost:6379' })
// RedisRateLimiter lazily connects to Redis when needed.
@@ -127,7 +234,11 @@ const limiter = new RedisRateLimiter(client, {
})
```
-````ts upstash
+### Upstash
+
+Delegates to an `@upstash/ratelimit` instance, so the algorithm and limits are configured there. A good fit for serverless and edge runtimes.
+
+````ts
import { Ratelimit } from '@upstash/ratelimit'
import { Redis } from '@upstash/redis'
import { UpstashRateLimiter } from '@orpc/ratelimit/upstash'
@@ -170,7 +281,11 @@ const limiter = new UpstashRateLimiter(ratelimit, {
})
````
-```ts bun
+### Bun Redis
+
+The Redis adapter for Bun's built-in Redis client, with no extra dependency. It shares counters with `RedisRateLimiter`.
+
+```ts
import { BunRedisRateLimiter } from '@orpc/bun'
import { redis } from 'bun'
@@ -208,7 +323,11 @@ const limiter = new BunRedisRateLimiter(redis, {
})
```
-```ts cloudflare
+### Cloudflare
+
+Uses the Workers Rate Limiting binding, so limits are configured in your Worker settings rather than in the adapter.
+
+```ts
import { CloudflareRateLimiter } from '@orpc/cloudflare'
export default {
@@ -224,106 +343,3 @@ export default {
}
}
```
-
-
-
-### Blocking Mode
-
-Some adapters support blocking mode, which waits until capacity becomes available instead of rejecting requests immediately.
-
-```ts
-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](/docs/procedure).
-
-```ts
-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 } }
-)
-```
-
-:::info[Automatic Deduplication]
-When the same `limiter` and `key` combination is used multiple times in a single request chain, the `ratelimit` middleware performs the rate limit check only once. This behavior follows the [Dedupe Middleware](/docs/recipes/dedupe-middleware) recipe. To disable deduplication, set `dedupe: false`.
-:::
-
-:::tip[Conditional Limiter]
-You can choose different limiters dynamically based on the request context:
-
-```ts
-const premiumLimiter = new MemoryRateLimiter({
- maxRequests: 100,
- window: 60000,
-})
-
-const standardLimiter = new MemoryRateLimiter({
- maxRequests: 10,
- window: 60000,
-})
-
-const result = await call(
- procedure,
- { email: 'user@example.com' },
- {
- context: {
- ratelimiter: isPremiumUser ? premiumLimiter : standardLimiter,
- },
- },
-)
-```
-
-:::
-
-## Handler Plugin
-
-The `RateLimitHandlerPlugin` automatically adds HTTP rate limiting headers (`RateLimit-*` and `Retry-After`) to responses when used with [Ratelimit Middleware](#ratelimit-middleware). This lets clients inspect the current limit state and know when they can retry after hitting a limit.
-
-```ts
-import { RateLimitHandlerPlugin } from '@orpc/ratelimit'
-
-const handler = new RPCHandler(router, {
- plugins: [
- new RateLimitHandlerPlugin(),
- ],
-})
-```
-
-:::info
-You can combine this plugin with [Retry After Plugin](/docs/plugins/retry-after) to enable automatic client-side retries based on server rate limiting headers.
-:::
-
-:::info
-The `handler` can be any supported oRPC handler, such as [RPCHandler](/docs/rpc/handler), [OpenAPIHandler](/docs/openapi/handler), or a custom one.
-:::
diff --git a/packages/ratelimit/src/adapters/redis.ts b/packages/ratelimit/src/adapters/redis.ts
index 829fc13bc..9e43da043 100644
--- a/packages/ratelimit/src/adapters/redis.ts
+++ b/packages/ratelimit/src/adapters/redis.ts
@@ -1,4 +1,4 @@
-import type { RedisClientType } from 'redis'
+import type { RedisClientType, RedisClusterType } from 'redis'
import type { RateLimiter, RateLimitOptions, RateLimitResult } from '../types'
import { sleep } from '@orpc/shared'
@@ -63,7 +63,7 @@ export interface RedisRateLimiterOptions {
* @see {@link https://orpc.dev/docs/helpers/ratelimit#adapters | Rate Limit Helpers - Adapters}
*/
export class RedisRateLimiter implements RateLimiter {
- private readonly redis: RedisClientType
+ private readonly redis: RedisClientType | RedisClusterType
private readonly prefix: string
private readonly maxRequests: number
private readonly window: number
@@ -72,7 +72,7 @@ export class RedisRateLimiter implements RateLimiter {
private scriptSha: undefined | Awaited>
constructor(
- redis: RedisClientType,
+ redis: RedisClientType | RedisClusterType,
options: RedisRateLimiterOptions,
) {
this.redis = redis