diff --git a/packages/documentation/content/docs/gateway/other-features/security/rate-limiting.mdx b/packages/documentation/content/docs/gateway/other-features/security/rate-limiting.mdx
index 7dc8ce98..73717ee3 100644
--- a/packages/documentation/content/docs/gateway/other-features/security/rate-limiting.mdx
+++ b/packages/documentation/content/docs/gateway/other-features/security/rate-limiting.mdx
@@ -1,16 +1,82 @@
---
title: Rate Limiting
+description:
+ Learn how to protect your Hive Gateway from abuse by configuring field-level rate limiting, using
+ either programmatic configuration or the @rateLimit directive in your subgraph schema.
searchable: false
---
-Rate limiting is a technique for reducing server load by limiting the number of requests that can be
-made to a subgraph.
+import { Callout } from "@hive/design-system/hive-components/callout";
-You can use rate limiting feature in order to limit the rate of calling queries and mutations.
+Rate limiting is a common API security practice that protects your gateway and upstream subgraphs
+from being overwhelmed by too many requests. Because GraphQL is highly flexible (clients choose
+exactly which fields to query), rate limiting needs to be applied at the **field level** rather than
+just on HTTP endpoints.
+
+Hive Gateway supports two complementary approaches:
+
+1. **Programmatic configuration**: define rate limits directly in `gateway.config.ts`.
+2. **`@rateLimit` directive**: annotate fields in your subgraph schemas.
+
+
+ By default, rate limiting state is kept **in-memory** and is local to each
+ gateway instance. For deployments with multiple gateway instances, configure a
+ shared [Redis cache](#distributed-rate-limiting-with-redis) so limits are
+ enforced consistently across all instances.
+
## Programmatic Configuration
+Use `rateLimiting` with an array of rules to configure per-field limits directly in
+`gateway.config.ts`. Each rule targets a specific GraphQL type and field, and lets you customize
+the time window, request limit, and how callers are identified.
+
+### Limit by Authorization Token
+
+A common pattern is to rate-limit each unique user identified by their authorization token:
+
+```ts title="gateway.config.ts"
+import { defineConfig } from "@graphql-hive/gateway";
+
+export const gatewayConfig = defineConfig({
+ rateLimiting: [
+ {
+ type: "Query",
+ field: "searchProducts",
+ max: 10, // allow at most 10 calls…
+ ttl: 60000, // …per 60 seconds (in milliseconds)
+ identifier: "{context.headers.authorization}", // per unique token
+ },
+ ],
+});
+```
+
+### Limit by IP Address
+
+If your gateway does not have an authenticated user, you can use the request IP address as the
+identifier:
+
+```ts title="gateway.config.ts"
+import { defineConfig } from "@graphql-hive/gateway";
+
+export const gatewayConfig = defineConfig({
+ rateLimiting: [
+ {
+ type: "Mutation",
+ field: "createComment",
+ max: 5,
+ ttl: 60000, // 60 seconds
+ identifier: "{context.request.headers.x-forwarded-for}",
+ },
+ ],
+});
+```
+
+### Multiple Rules
+
+You can define rate limits for multiple fields at once:
+
```ts title="gateway.config.ts"
import { defineConfig } from "@graphql-hive/gateway";
@@ -18,17 +84,39 @@ export const gatewayConfig = defineConfig({
rateLimiting: [
{
type: "Query",
- field: "foo",
- max: 5, // requests limit for a time period
- ttl: 5000, // time period
- // You can use any value from the context
+ field: "searchProducts",
+ max: 10,
+ ttl: 60000,
+ identifier: "{context.headers.authorization}",
+ },
+ {
+ type: "Mutation",
+ field: "createOrder",
+ max: 3,
+ ttl: 60000,
identifier: "{context.headers.authorization}",
},
],
});
```
-## Rate Limiting through `@rateLimit` directive
+### Configuration Options
+
+| Option | Type | Description |
+| ------------ | -------- | --------------------------------------------------------------------------------------------------------------- |
+| `type` | `string` | The GraphQL type that contains the field to rate-limit (e.g. `"Query"`, `"Mutation"`). |
+| `field` | `string` | The field name on the given type to rate-limit. |
+| `max` | `number` | Maximum number of requests allowed within the `ttl` window. |
+| `ttl` | `number` | Duration of the time window in **milliseconds** (e.g. `60000` for 1 minute). |
+| `identifier` | `string` | Template string for identifying the caller. Use `{context.*}` to access request context values such as headers. |
+
+## Rate Limiting through `@rateLimit` Directive
+
+When using Federation, you can annotate fields directly in your subgraph schemas with the
+`@rateLimit` directive. This keeps rate-limit intent close to the schema definition and requires
+minimal gateway configuration.
+
+### Step 1: Enable directive-based rate limiting in the gateway
```ts title="gateway.config.ts"
import { defineConfig } from "@graphql-hive/gateway";
@@ -38,14 +126,12 @@ export const gatewayConfig = defineConfig({
});
```
-This approach follows the pattern of
-[`graphql-rate-limit`](https://github.com/teamplanes/graphql-rate-limit/blob/master/README.md#field-config).
+### Step 2: Add the directive definition to your subgraph
-To set rate limit hints in your subgraph schema, the `@rateLimit` directive definition should be
-included in the subgraph schema:
+Include the `@rateLimit` directive definition and import it via Federation's `@composeDirective` so
+the gateway picks it up:
-```graphql
-# Import the directive for Federation
+```graphql title="subgraph.graphql"
extend schema
@link(url: "https://specs.apollo.dev/link/v1.0")
@link(
@@ -67,30 +153,92 @@ directive @rateLimit(
) on FIELD_DEFINITION
```
-Then in the subgraph schema, you can use the `@rateLimit` directive to set rate limit hints on
-fields:
+### Step 3: Annotate fields
+
+Apply the `@rateLimit` directive to any field you want to protect:
-```graphql
+```graphql title="subgraph.graphql"
type Query {
getItems: [Item]
@rateLimit(window: "1s", max: 5, message: "You are doing that too often.")
+
+ searchProducts(query: String!): [Product] @rateLimit(window: "1m", max: 30)
+}
+
+type Mutation {
+ createOrder(input: OrderInput!): Order
+ @rateLimit(
+ window: "1m"
+ max: 5
+ message: "Too many orders, please slow down."
+ )
+}
+```
+
+### Limit per Argument (e.g. per ID)
+
+Use `identityArgs` to rate-limit access per field argument, for example, limiting how often each
+unique product ID can be fetched:
+
+```graphql title="subgraph.graphql"
+type Query {
+ getProduct(id: ID!): Product
+ @rateLimit(window: "1m", max: 10, identityArgs: ["id"])
+}
+```
+
+### Limit by Array Length
+
+For mutations or queries that accept arrays, you can count each element in the array as a separate
+call toward the limit using `arrayLengthField`:
+
+```graphql title="subgraph.graphql"
+type Mutation {
+ bulkCreateItems(items: [ItemInput!]!): [Item]
+ @rateLimit(window: "1m", max: 100, arrayLengthField: "items")
}
```
-## Field Configuration
+### Directive Field Reference
+
+| Option | Type | Description |
+| ------------------ | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
+| `window` | `string` | Time interval for the rate limit window. Accepts human-readable durations such as `"1s"`, `"30s"`, `"1m"`, `"1h"`. |
+| `max` | `number` | Maximum number of calls to the field allowed within the `window`. |
+| `identityArgs` | `[string]` | Field argument names used to distinguish callers. For example, `["id"]` rate-limits each unique value of the `id` argument separately. Supports nested paths via dot notation (e.g. `"input.userId"`). |
+| `message` | `string` | Custom error message returned when the rate limit is exceeded. |
+| `arrayLengthField` | `string` | Name of an array argument whose length is counted as the number of calls (useful for bulk operations). |
-- `window`: Specify a time interval window that the max number of requests can access the field. We
- use Zeit's ms to parse the window arg, docs here.
+## Distributed Rate Limiting with Redis
-- `max`: Define the max number of calls to the given field per window.
+By default, rate limit counters are stored in memory on each gateway instance. In a multi-instance
+deployment this means every instance has its own independent counter, so the effective rate limit is
+multiplied by the number of instances.
-- `identityArgs`: If you wanted to limit the requests to a field per id, per user, use identityArgs
- to define how the request should be identified. For example you'd provide just ["id"] if you
- wanted to rate limit the access to a field by id. We use Lodash's get to access nested identity
- args, docs here.
+To enforce rate limits consistently across all gateway instances, configure a shared Redis cache:
-- `message`: A custom message per field. Note you can also use formatError to customise the default
- error message if you don't want to define a single message per rate limited field.
+```ts title="gateway.config.ts"
+import { defineConfig } from "@graphql-hive/gateway";
+
+export const gatewayConfig = defineConfig({
+ cache: {
+ type: "redis",
+ url: "redis://localhost:6379",
+ },
+ rateLimiting: [
+ {
+ type: "Query",
+ field: "searchProducts",
+ max: 10,
+ ttl: 60000,
+ identifier: "{context.headers.authorization}",
+ },
+ ],
+});
+```
-- `arrayLengthField`: Limit calls to the field, using the length of the array as the number of calls
- to the field.
+
+ The Redis cache option currently only works in Node.js environments. See the
+ [Performance & Caching](/docs/gateway/other-features/performance) page for all
+ available cache backends and configuration options.
+