Dynamic Rate Limiting
Static rate limits apply the same threshold to every caller. Dynamic rate limiting lets you determine limits at request time — so premium customers get higher throughput, free-tier users get a lower ceiling, and internal services can bypass limits entirely.
Dynamic rate limiting works with both the Rate Limiting policy and the Complex Rate Limiting policy.
How it works
When you set rateLimitBy to "function", the policy calls a TypeScript
function you provide on every request. That function returns a
CustomRateLimitDetails object that tells the rate limiter:
key— The string used to group requests into buckets (e.g., a user ID or API key consumer name).requestsAllowed(optional) — Overrides the policy's defaultrequestsAllowedfor this request.timeWindowMinutes(optional) — Overrides the policy's defaulttimeWindowMinutesfor this request.
Returning undefined skips rate limiting for that request entirely.
Create a rate limit function
Create a new module (for example modules/rate-limit.ts) with a function that
inspects the request and returns the appropriate limits.
The following example reads a customerType field from the authenticated user's
metadata and applies different limits per tier:
modules/rate-limit.ts
When using API key authentication, the
user.data object contains the metadata you set when creating the API key
consumer. When using JWT authentication, it contains the decoded token claims.
Configure the policy
Wire the function into the rate limiting policy by setting rateLimitBy to
"function" and pointing the identifier option at your module:
config/policies.json
The requestsAllowed and timeWindowMinutes values in the policy configuration
serve as defaults. Your function can override them per request, or omit them to
use the defaults.
Common patterns
Tier-based limits from API key metadata
Store a plan or customerType field in your API key consumer metadata, then
branch on it in your rate limit function. This is the simplest approach and
requires no external lookups.
Route-based limits
Use request.url or request.params to apply different limits to different
endpoints. For example, a search endpoint might allow 10 requests per minute
while a read endpoint allows 100.
Code
Method-based limits
Apply different limits to read operations (GET) vs. write operations (POST, PUT, DELETE). Write-heavy endpoints often need tighter limits to protect backends:
Code
Database-driven limits
For limits that change frequently or are managed outside your gateway configuration, look them up from a database at request time. Use the ZoneCache to avoid hitting the database on every request.
See Per-user rate limiting with a database for a complete example using Supabase and ZoneCache.
Skip rate limiting for specific requests
Return undefined to bypass rate limiting entirely. This is useful for health
checks, internal services, or admin users:
Code
Testing
To verify that dynamic limits are applied correctly, create API key consumers
with different metadata values (for example, one with
{"customerType": "premium"} and one with {"customerType": "free"}).
Make requests with each key until you receive a 429 Too Many Requests
response. For example, with a free-tier key limited to 50 requests per minute:
Code
The first 50 requests return 200. Requests 51-55 return 429 with a
Retry-After header. Repeat with the premium key and confirm the higher limit
applies.
Rate limit counters are per-environment. Preview and development environments have their own counters separate from production, so testing does not affect production limits.
Related resources
- How rate limiting works — Full explanation of
rateLimitBymodes and configuration options - Rate Limiting policy reference
- Per-user rate limiting with a database — Advanced example with database lookups and caching