Skip to main content
This guide walks you through setting up and using the Convex Rate Limiter component with real examples.
1

Install the Package

First, install the rate limiter component:
npm install @convex-dev/rate-limiter
2

Configure convex.config.ts

Create or update convex/convex.config.ts to register the component:
convex/convex.config.ts
import { defineApp } from "convex/server";
import rateLimiter from "@convex-dev/rate-limiter/convex.config.js";

const app = defineApp();
app.use(rateLimiter);

export default app;
3

Define Your Rate Limits

Create a new file (e.g., convex/rateLimits.ts) and define your rate limits:
convex/rateLimits.ts
import { RateLimiter, MINUTE, HOUR } from "@convex-dev/rate-limiter";
import { components } from "./_generated/api";

export const rateLimiter = new RateLimiter(components.rateLimiter, {
  // A per-user limit, allowing one every ~6 seconds.
  // Allows up to 3 in quick succession if they haven't sent many recently.
  sendMessage: { kind: "token bucket", rate: 10, period: MINUTE, capacity: 3 },
  
  // One global / singleton rate limit for free trial signups
  freeTrialSignUp: { kind: "fixed window", rate: 100, period: HOUR },
});
Rate limit strategies:
  • Token bucket: Tokens are added at a steady rate, allowing bursts up to capacity
  • Fixed window: All tokens granted at once every period, with optional rollover
4

Use in a Mutation - Global Rate Limit

Apply a global rate limit that applies to all users:
convex/users.ts
import { mutation } from "./_generated/server";
import { rateLimiter } from "./rateLimits";

export const signUpForFreeTrial = mutation({
  args: { /* ... */ },
  handler: async (ctx, args) => {
    // Check global rate limit for free trial signups
    const { ok, retryAfter } = await rateLimiter.limit(ctx, "freeTrialSignUp");
    
    if (!ok) {
      throw new Error(
        `Rate limit exceeded. Please try again in ${Math.ceil(retryAfter! / 1000)} seconds.`
      );
    }
    
    // Proceed with signup logic...
  },
});
5

Use in a Mutation - Per-User Rate Limit

Apply a rate limit specific to each user using a key:
convex/messages.ts
import { mutation } from "./_generated/server";
import { v } from "convex/values";
import { rateLimiter } from "./rateLimits";

export const sendMessage = mutation({
  args: {
    text: v.string(),
  },
  handler: async (ctx, args) => {
    const user = await ctx.auth.getUserIdentity();
    if (!user) {
      throw new Error("Not authenticated");
    }
    
    // Rate limit per user
    const { ok, retryAfter } = await rateLimiter.limit(ctx, "sendMessage", {
      key: user.subject,
    });
    
    if (!ok) {
      throw new Error(
        `You're sending messages too quickly. Please wait ${Math.ceil(retryAfter! / 1000)} seconds.`
      );
    }
    
    // Save the message
    await ctx.db.insert("messages", {
      text: args.text,
      userId: user.subject,
      timestamp: Date.now(),
    });
  },
});
6

Error Handling with throws Option

For cleaner code, use the throws option to automatically throw errors when rate limits are exceeded:
convex/auth.ts
import { mutation } from "./_generated/server";
import { v } from "convex/values";
import { RateLimiter, HOUR, isRateLimitError } from "@convex-dev/rate-limiter";
import { components } from "./_generated/api";

const rateLimiter = new RateLimiter(components.rateLimiter, {
  failedLogins: { kind: "token bucket", rate: 10, period: HOUR },
});

export const login = mutation({
  args: {
    email: v.string(),
    password: v.string(),
  },
  handler: async (ctx, args) => {
    try {
      // Automatically throw if rate limit is exceeded
      await rateLimiter.limit(ctx, "failedLogins", {
        key: args.email,
        throws: true,
      });
      
      // Attempt login...
      const success = await attemptLogin(args.email, args.password);
      
      if (success) {
        // Reset rate limit on successful login
        await rateLimiter.reset(ctx, "failedLogins", { key: args.email });
      }
      
      return { success };
    } catch (error) {
      if (isRateLimitError(error)) {
        throw new Error(
          `Too many failed login attempts. Please try again later.`
        );
      }
      throw error;
    }
  },
});

function attemptLogin(email: string, password: string) {
  // Your login logic here
  return true;
}
When using throws: true, the rate limiter throws a ConvexError with data {kind: "RateLimited", name, retryAfter}. Use isRateLimitError() to check if an error is a rate limit error.

What You’ve Learned

You now know how to:
  • Install and configure the Convex Rate Limiter component
  • Define rate limits with different strategies (token bucket and fixed window)
  • Apply global rate limits that affect all users
  • Apply per-user rate limits using keys
  • Handle rate limit errors gracefully
  • Reset rate limits when needed

Next Steps

Explore more advanced features:
  • Custom token counts: Consume multiple tokens in a single request for LLM token limiting
  • Sharding: Scale to high throughput with configurable sharding
  • Reservations: Reserve capacity to avoid starvation on larger requests
  • React hook: Check rate limits from your frontend with useRateLimit
For detailed information, check out the full Stack post on rate limiting.

Build docs developers (and LLMs) love