Skip to content
Edge Functions

Recursive / Nested Function Calls

Understanding rate limits when Edge Functions invoke each other

Edge Functions can call other Edge Functions using fetch(). This enables powerful patterns like function chaining, fan-out/fan-in workflows, and recursive processing. To protect platform stability and prevent runaway amplification, Supabase rate limits these internal function-to-function calls.

What gets rate limited#

Rate limiting applies to outbound fetch() calls made by your Edge Functions to other Edge Functions within your project. This includes:

  • Direct recursion: A function calling itself
  • Function chaining: Function A calling Function B
  • Circular calls: Function A calling Function B, which calls Function A
  • Fan-out patterns: A function calling multiple other functions concurrently

Rate limit budget#

Each request chain has a budget of at least 5,000 requests per minute. In busier regions, this budget may be higher. All function-to-function calls within the same request chain share this budget.

For example, if Function A calls Function B, and Function B calls Function C, all three calls count toward the same budget pool.

Handling rate limit errors#

When the rate limit is exceeded, calling another Edge Function throws a RateLimitError. This error includes a retryAfterMs property indicating how long to wait (in milliseconds) before retrying. You should catch this error and handle it gracefully:

import { createClient } from 'jsr:@supabase/supabase-js@2'
const SUPABASE_PUBLISHABLE_KEYS = JSON.parse(Deno.env.get('SUPABASE_PUBLISHABLE_KEYS')!)
const supabase = createClient(
Deno.env.get('SUPABASE_URL')!,
// If you want to use a different api key, change 'default' to your preferred key name
SUPABASE_PUBLISHABLE_KEYS['default']
)
Deno.serve(async (req) => {
try {
const { data, error } = await supabase.functions.invoke('other-function', {
body: { foo: 'bar' },
})
if (error) throw error
return new Response(JSON.stringify(data), {
headers: { 'Content-Type': 'application/json' },
})
} catch (err) {
if (err instanceof Deno.errors.RateLimitError) {
// Use retryAfterMs to tell the client when to retry
const retryAfterSeconds = Math.ceil(err.retryAfterMs / 1000)
return new Response(
JSON.stringify({ error: 'Service temporarily unavailable. Please retry later.' }),
{
status: 429,
headers: {
'Content-Type': 'application/json',
'Retry-After': retryAfterSeconds.toString(),
},
}
)
}
throw err
}
})

You can also use retryAfterMs to implement automatic retries within your function:

import { createClient } from 'jsr:@supabase/supabase-js@2'
const SUPABASE_PUBLISHABLE_KEYS = JSON.parse(Deno.env.get('SUPABASE_PUBLISHABLE_KEYS')!)
const supabase = createClient(
Deno.env.get('SUPABASE_URL')!,
// If you want to use a different api key, change 'default' to your preferred key name
SUPABASE_PUBLISHABLE_KEYS['default']
)
async function invokeWithRetry(functionName: string, payload: object, maxRetries = 3) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
const { data, error } = await supabase.functions.invoke(functionName, {
body: payload,
})
if (error) throw error
return data
} catch (err) {
if (err instanceof Deno.errors.RateLimitError && attempt < maxRetries - 1) {
// Wait for the recommended duration before retrying
await new Promise((resolve) => setTimeout(resolve, err.retryAfterMs))
continue
}
throw err
}
}
}

Tips for avoiding rate limits#

1. Batch operations instead of individual calls#

Instead of calling a function once per item, batch multiple items into a single call:

// ❌ Avoid: One call per item
for (const item of items) {
await supabase.functions.invoke('process-item', { body: item })
}
// ✅ Better: Batch items into one call
await supabase.functions.invoke('process-items', { body: { items } })

2. Limit recursion depth#

If your function is recursive, set a maximum depth to prevent unbounded call chains:

Deno.serve(async (req) => {
const { depth = 0, data } = await req.json()
if (depth >= 5) {
// Stop recursion at max depth
return new Response(JSON.stringify({ result: data }))
}
// Process and recurse with incremented depth
const processed = processData(data)
const { data: result } = await supabase.functions.invoke('my-function', {
body: { depth: depth + 1, data: processed },
})
return new Response(JSON.stringify(result))
})

3. Use queues for large workloads#

For processing large datasets, consider using Supabase Queues instead of recursive function calls. Queues handle backpressure automatically and are better suited for high-volume workloads.

4. Use shared libraries instead of separate functions#

Instead of creating separate Edge Functions that call each other, create a shared library of functions and import them directly. This avoids HTTP overhead and rate limits entirely:

// supabase/functions/_shared/transform.ts
export function validate(data: any) {
// validation logic
}
export function transform(data: any) {
// transformation logic
}
export async function save(data: any) {
// save logic
}
// supabase/functions/process-data/index.ts
import { save, transform, validate } from '../_shared/transform.ts'
Deno.serve(async (req) => {
const data = await req.json()
const validated = validate(data)
const transformed = transform(validated)
const result = await save(transformed)
return new Response(JSON.stringify(result))
})

5. Add delays for non-urgent processing#

If immediate processing isn't required, add delays between calls to spread the load:

async function processWithDelay(items: any[]) {
for (const item of items) {
await supabase.functions.invoke('process-item', { body: item })
await new Promise((resolve) => setTimeout(resolve, 100)) // 100ms delay
}
}

Common patterns and their impact#

PatternBudget consumptionRecommendation
Basic chain (A to B to C)LowGenerally safe
Fan-out (A to B, C, D, E)ModerateLimit concurrency
Deep recursion (A to A to A...)HighSet max depth
Unbounded loopsVery highAvoid, use queues

Increasing rate limits#

Currently, all plans have the same rate limit budget. We are working on introducing custom limits for different use cases.

If you need a higher rate limit for your project, contact support with details about your use case.