Implement Supabase rate limiting, backoff, and idempotency patterns. Use when handling rate limit errors, implementing retry logic, or optimizing API request throughput for Supabase. Trigger with...
Supabase enforces rate limits and quotas across every API surface β PostgREST, Auth, Storage, Realtime, and Edge Functions β and the numbers scale by plan tier. This skill gives you the exact per-tier limits, connection pooling via Supavisor, retry/backoff and pagination patterns, and dashboard monitoring so you stay within quota and handle 429 errors gracefully.
@supabase/supabase-js v2+ installedRate limits differ per surface and per plan. The headline API limits:
| Metric | Free | Pro | Enterprise |
|---|---|---|---|
| Requests per minute (RPM) | 500 | 5,000 | Unlimited (custom) |
| Requests per day (RPD) | 50,000 | 1,000,000 | Unlimited (custom) |
Auth, Storage, Realtime, Edge Functions, and Database connections each carry their own quotas. See the full per-surface breakdown in rate-limit-tiers.md before you architect.
Supavisor is Supabase's built-in connection pooler (replaced PgBouncer). Pick the mode by workload:
| Use case | Mode | Port |
|---|---|---|
| Serverless / Edge Functions | Transaction | 6543 |
| Next.js API routes | Transaction | 6543 |
| Long-running workers | Session | 5432 |
| Realtime subscriptions | Direct (no pooler) | 5432 |
| Prisma / Drizzle ORM | Transaction + ?pgbouncer=true |
6543 |
Transaction mode (port 6543) returns a connection to the pool after each transaction β the right default for serverless. Session mode (port 5432) holds a dedicated connection for LISTEN/NOTIFY and prepared statements. Full client setup and connection-string formats are in implementation.md.
Wrap queries in an exponential-backoff retry that recognizes 429s and pool exhaustion:
// Retryable when the error is a rate limit, "too many requests",
// code 429, or PGRST000 (connection pool exhausted). Delay doubles
// per attempt with jitter, capped at maxDelayMs, honoring Retry-After.
const users = await withRetry(() =>
supabase.from('users').select('id, email, created_at').eq('active', true)
)
Then cut request volume two ways: paginate large reads with .range(from, to) so responses stay small and avoid timeouts, and collapse N writes into one batch upsert (max ~1000 rows/request, chunk larger sets). The full withRetry, fetchPaginated, and batch/chunk helpers β plus dashboard monitoring steps β are in implementation.md.
After applying this skill you will have:
.range(0, 99) to reduce payload size| Error | Cause | Solution |
|---|---|---|
429 Too Many Requests |
Exceeded RPM or RPD limit | Apply withRetry backoff; reduce concurrency; upgrade tier |
PGRST000: could not connect |
Connection pool exhausted | Switch to Supavisor transaction mode (port 6543); reduce concurrent queries |
Auth over_request_rate_limit |
Too many signups/logins from one IP | Add CAPTCHA; configure custom auth rate limits in Dashboard |
Storage 413 Payload Too Large |
File exceeds tier limit | Use TUS resumable upload; check tier file size limit |
Realtime too_many_connections |
Concurrent connection limit reached | Unsubscribe unused channels; upgrade to Pro for 500 connections |
Edge Function BOOT_ERROR |
Cold start timeout or memory exceeded | Reduce bundle size; avoid large imports at top level |
pgbouncer=true errors with Prisma |
Missing connection string parameter | Append ?pgbouncer=true to pooler connection string on port 6543 |
Rate-limit response headers (X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, Retry-After) and how to act on each are in errors.md.
For securing your Supabase project with RLS policies and API key management, see supabase-security-basics. For optimizing database queries and indexing, see supabase-performance-tuning.