Supabase Edge Functions: Handling Retries, Timeouts, and Partial Failures
Learn how to handle retries, timeouts, and partial failures in Supabase Edge Functions with practical TypeScript examples.

Supabase Edge Functions are commonly used for APIs, webhooks, database operations, and third-party integrations. Once a function depends on a network, failures are guaranteed: a request fails, an API responds too slowly, or one operation succeeds while another fails. This guide covers Supabase Edge Functions retries, timeouts, and partial failures - three problems, three fixes - with working TypeScript.
Quick Answer
Handle each failure type differently: built-in retries for PostgREST, custom retries only when needed, explicit timeouts for slow requests, and independent operations handled separately so one failure doesn't hide successful work.
What Is It?
An Edge Function often sits between your app and other services:

A failure can happen at any of these points:
- A database request may temporarily fail
- An external API may respond too slowly
- One of several operations may succeed while another fails
| Problem | What happens | Main approach |
|---|---|---|
| Retry | Temporary failure | Try again |
| Timeout | Operation takes too long | Stop waiting |
| Partial failure | Some operations succeed, others fail | Handle each result separately |
Skip this if your function only reads static data; otherwise, at least one pattern here applies to you.
The Problem
An Edge Function calling Postgres, a third-party API, or Storage bets every call succeeds quickly - and eventually one won't. A function that emails a user, logs an event, and updates a CRM can succeed at two and fail at the third. Without explicit handling, a temporary blip becomes permanent, a slow dependency eats the function's runtime, and one failed operation erases three successful ones.
The Solution
Each problem has its own fix: retries for transient failures, explicit timeouts for slow dependencies, and Promise.allSettled() for independent operations. The rest of this guide covers each one with working TypeScript.
Prerequisites
- A Supabase project with Edge Functions enabled
- supabase-js v2.102.0 or later, for the built-in PostgREST retry behavior
- The Supabase CLI, for local development and deployment
- Basic TypeScript/JavaScript, including async/await and Promises
Handling Retries in Supabase Edge Functions
Retries are useful when a failure is temporary. For example:
But retrying every error is a bad idea: a permanent error just fails again, and an unsafe retry can duplicate work.
Supabase Has Built-In Retries for PostgREST
Since supabase-js v2.102.0, PostgREST queries using .from() and .rpc() retry automatically by default, using exponential backoff with jitter for transient failures.
The built-in retry behavior covers:
- 408 Request Timeout
- 409 Conflict
- 503 Service Unavailable
- 504 Gateway Timeout
- Network failures
Supabase retries idempotent methods (GET, HEAD, OPTIONS) plus the POST requests PostgREST uses. For example:
const { data, error } = await supabase
.from("users")
.select("*");
You don't need a retry loop around every PostgREST query - Supabase recommends the built-in mechanism for most cases.
When Do You Need Custom Retries?
The built-in mechanism doesn't cover every request. Custom retries are needed for:
- Edge Functions
- Auth
- Storage
- Other non-PostgREST requests
Supabase documents using fetch-retry around the client's fetch implementation. For example:
npm install @supabase/supabase-js fetch-retryThen:
import { createClient } from "@supabase/supabase-js";
import fetchRetry from "fetch-retry";
const fetchWithRetry = fetchRetry(fetch, {
retries: 3,
retryDelay: (attempt) =>
Math.min(1000 * 2 ** attempt, 30000),
retryOn: [503, 504],
});
const supabase = createClient(
SUPABASE_URL,
SUPABASE_KEY,
{
global: {
fetch: fetchWithRetry,
},
},
);Retry conditions should depend on the request.
Use Exponential Backoff
When custom retries are necessary, increase the delay between attempts instead of retrying immediately:

Exponential backoff reduces pressure on a failing service; jitter keeps concurrent requests from retrying at once. Supabase's built-in retries already use both.
Don't Retry Everything
Usually retry:
- Network failures
- 408
- 503
- 504
- Temporary rate-limit responses, when the service tells you to retry
Usually don't retry:
- Invalid input
- Authentication failures
- Authorization failures
- Business-rule errors
Avoid retrying indefinitely, too - too many retries can exhaust the Data API connection pool and reduce throughput.
Handling Timeouts in Supabase Edge Functions
A timeout happens when an operation takes longer than allowed - common when an Edge Function calls an external API:

A timeout doesn't always mean the operation failed - the response may have been delayed or lost.
Know the Edge Function Runtime Limits
Supabase currently documents these hosted limits:
- 150 seconds request idle timeout
- 150 seconds wall-clock limit on Free
- 400 seconds wall-clock limit on paid plans
- 256 MB maximum memory
- 2 seconds CPU time per request
If the function doesn't respond within 150 seconds, Supabase returns a 504. This limit can't be increased, so optimize the work instead of waiting longer.
Don't treat 150 seconds as a target - give slow dependencies their own, smaller timeout.
Set a Timeout for External Requests
Use AbortController to cap how long a fetch() call can run:
async function fetchWithTimeout(
url: string,
timeoutMs = 8000,
) {
const controller = new AbortController();
const timer = setTimeout(() => {
controller.abort();
}, timeoutMs);
try {
return await fetch(url, {
signal: controller.signal,
});
} finally {
clearTimeout(timer);
}
}Then:
const response = await fetchWithTimeout(
"https://api.example.com/process",
8000,
);Now the API gets an eight-second deadline instead of the function's entire runtime.
Why a 504 Shouldn't Automatically Trigger a Retry
An internal 504 means the function didn't start its response within the 150-second idle timeout - usually a slow database query or external API.
Possible causes:
- Slow external API
- Slow database query
- Too many sequential operations
- Infinite or blocking code
- Too much work inside one request
Decide whether to retry only after identifying the cause.
Give External Services Their Own Timeout
An Edge Function calling three services should give each dependency its own deadline instead of letting it wait indefinitely:

Handling Partial Failures in Supabase Edge Functions
A partial failure happens when a function runs multiple operations and only some fail. For example:
Send email ✓
Create notification ✓
Update CRM ✗
Save analytics ✓
The whole function didn't fail; three operations succeeded, one didn't. Treating it as one unit loses that information.
The Problem With Promise.all()
Consider:
const results = await Promise.all([
sendEmail(),
createNotification(),
updateCRM(),
]);If updateCRM() fails, Promise.all() rejects the whole batch, hiding which operations succeeded. Promise.allSettled() is usually the better choice for independent operations.
Use Promise.allSettled() for Independent Operations
Each operation now gets its own result. For example, mapping the results to a clean response:
const results = await Promise.allSettled([
sendEmail(),
createNotification(),
updateCRM(),
]);
const response = results.map((result, index) => ({
operation: ["email", "notification", "crm"][index],
success: result.status === "fulfilled",
}));
return Response.json(response);The result can tell the caller:
[
{ "operation": "email", "success": true },
{ "operation": "notification", "success": true },
{ "operation": "crm", "success": false }
]Now the failed operation can be handled without the whole batch looking like a failure.
Don't Use Promise.allSettled() for Dependent Operations
Not every group of operations is independent. For example:
Create user
↓
Create profile
↓
Create subscriptionThe second operation needs the first one's result, so sequential execution is clearer:
const user = await createUser();
const profile = await createProfile(user.id);
const subscription =
await createSubscription(profile.id);If user creation fails, there's no reason to continue.
Use Promise.allSettled() when operations are independent. Use sequential execution when one operation depends on another.
Retry Only the Failed Operation
This matters even more with retries. Suppose the email and notification above already succeeded and only the CRM update failed:
Retrying the whole request would re-send the email and re-create the notification. Retry only the operation that actually failed:

This keeps recovery smaller and avoids repeating successful work.
Common Problems
Retry keeps running
No attempt limit lets a failure eat the function's runtime. Fix: cap the attempts.
External API makes the function time out
A slow fetch() stalls until the platform timeout. Fix: use AbortController.
A 504 is treated as proof that the operation failed
A timeout means the response was late, not that the operation failed. Fix: be careful retrying operations with side effects.
One failed operation hides successful operations
Promise.all() on independent operations can make the whole group look failed. Fix: use Promise.allSettled() for individual results.
Too many retries reduce performance
Excessive retries can exhaust the Data API connection pool. Fix: keep retries bounded and appropriate.
Best Practices
- Use Supabase's built-in PostgREST retries where they fit - enabled by default since supabase-js v2.102.0.
- Use custom retries only when necessary, and configure them deliberately for non-PostgREST requests.
- Use exponential backoff and jitter, and cap retry attempts - more retries don't mean more reliability.
- Set explicit timeouts for external APIs. Don't let a dependency consume the entire runtime.
- Use Promise.allSettled() for independent operations so one failure doesn't erase the others' success.
- Retry only the failed operation when possible. Don't repeat work that already succeeded.
When Should You Use It?
Reach for these patterns when:
- It calls external APIs or runs multiple independent writes
- You've seen intermittent 503/504 errors or timeouts in production logs
- One failed operation makes the whole function report failure, even though most of it succeeded
- You're retrying requests without a backoff strategy or attempt limit
You can keep it simple when:
- It only makes PostgREST queries through supabase-js v2.102.0+
- Every operation depends on the one before it, so Promise.allSettled() doesn't apply
- The function completes in well under a second with no external dependencies
FAQ
Does Supabase automatically retry Edge Function requests?
Not all of them. supabase-js v2.102.0+ retries PostgREST .from()/.rpc() queries automatically; Functions, Auth, and Storage need custom fetch-retry configuration.
How long can a Supabase Edge Function run?
150 seconds for the idle timeout; wall-clock is 150 seconds on Free and 400 on paid plans.
How do I timeout an external API request in an Edge Function?
Use AbortController with fetch() and cancel the request after a set period.
Should I retry a 504 from an Edge Function?
Not automatically. Check whether it's a slow dependency or an operation that already had a side effect - a 504 means a timeout, not proof of failure.
When should I use Promise.allSettled()?
When operations are independent and you need to know which succeeded or failed individually.
Conclusion
Supabase Edge Functions retries, timeouts, and partial failures are three separate problems, each needing its own fix. Use the built-in supabase-js retry behavior for PostgREST, add custom retry logic only when needed, and set explicit timeouts instead of waiting for the platform timeout. When independent operations run together, use Promise.allSettled() so one failure doesn't hide successful work - treat every timeout and partial failure as information, not a simple pass/fail signal.
Soft CTA
Need help building reliable Supabase Edge Functions or integrating Supabase into production? Our team can help design, develop, and deploy backend workflows that handle retries, timeouts, and partial failures correctly. Contact us to discuss your project.

Aarav Sharma
Lead Software Engineer
Aarav leads product engineering at Matlab Infotech, where he has shipped mobile and web platforms across healthcare, fintech, and SaaS. He writes about pragmatic engineering and shipping fast without cutting corners.


