Supabase is a powerful Backend-as-a-Service platform, but CORS errors remain one of the most frustrating issues developers encounter. Whether you’re building with React, Vue, Angular, or vanilla JavaScript, you’ve likely seen errors like:
Access to fetch at 'https://your-project.supabase.co/...' from origin 'http://localhost:3000'
has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present.
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource.
(Reason: CORS header 'Access-Control-Allow-Origin' missing).
The challenge with Supabase CORS is that different components require different fixes:
- REST API (PostgREST) - Handles CORS automatically
- Edge Functions - Requires manual CORS configuration
- Storage - Has its own CORS considerations
- Auth - Usually works but can fail in specific scenarios
- Realtime - WebSocket-specific challenges
This comprehensive guide covers every type of Supabase CORS error and how to fix them—both through proper Supabase configuration and using corsproxy.io as a quick solution when you can’t modify server settings.
Table of Contents
- Quick Fix with corsproxy.io
- Understanding Supabase CORS Architecture
- Fix Edge Functions CORS Errors
- Fix REST API CORS Errors
- Fix Auth CORS Errors
- Fix Storage CORS Errors
- Fix Realtime WebSocket CORS Errors
- Fix Self-Hosted Supabase CORS Errors
- When to Use corsproxy.io vs. Native Fixes
- Troubleshooting Checklist
Quick Fix with corsproxy.io
Need to bypass a Supabase CORS error right now? If you’re blocked by CORS and can’t modify Edge Function code or Supabase configuration, corsproxy.io provides an instant workaround:
// Direct Supabase call blocked by CORS
const response = await fetch('https://your-project.supabase.co/functions/v1/my-function', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ data: 'test' })
});
// Using corsproxy.io - works immediately
const supabaseUrl = 'https://your-project.supabase.co/functions/v1/my-function';
const response = await fetch(`https://corsproxy.io/?url=${encodeURIComponent(supabaseUrl)}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ data: 'test' })
});
This works for:
- Edge Functions without CORS headers
- Third-party Supabase projects you don’t control
- Quick prototyping and development
- Testing before implementing proper CORS
However, for production applications, you should implement proper CORS handling. Keep reading to learn the correct fixes for each Supabase component.
Understanding Supabase CORS Architecture
Before diving into fixes, it’s crucial to understand how different Supabase services handle CORS:
| Service | CORS Handling | Your Control Level |
|---|---|---|
| REST API (PostgREST) | Automatic | Limited (proxy required for customization) |
| Edge Functions | Manual | Full control |
| Storage | Automatic with caveats | Limited |
| Auth (GoTrue) | Automatic | Limited |
| Realtime | WebSocket-based | Different rules apply |
Key insight from 2025: Supabase no longer provides dashboard settings to configure CORS headers. The previous “API” or “Database” tabs used for this are gone. If you need fine-grained CORS control, you’ll need to either:
- Handle it in Edge Functions
- Use a proxy layer like corsproxy.io
Fix Edge Functions CORS Errors
Edge Functions are the most common source of Supabase CORS errors because unlike the REST API, you must handle CORS manually.
The Problem
When invoking Edge Functions from the browser:
// This often fails with CORS error
const { data, error } = await supabase.functions.invoke('my-function', {
body: { name: 'World' }
});
Error:
Access to fetch at 'https://project.supabase.co/functions/v1/my-function'
has been blocked by CORS policy: Response to preflight request doesn't pass
access control check: No 'Access-Control-Allow-Origin' header is present.
Solution 1: Add CORS Headers to Your Edge Function (Recommended)
Create a shared CORS configuration file:
// supabase/functions/_shared/cors.ts
export const corsHeaders = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
'Access-Control-Allow-Methods': 'POST, GET, OPTIONS, PUT, DELETE',
};
Then use it in your Edge Function:
// supabase/functions/my-function/index.ts
import { corsHeaders } from '../_shared/cors.ts';
Deno.serve(async (req) => {
// CRITICAL: Handle OPTIONS preflight request FIRST
if (req.method === 'OPTIONS') {
return new Response('ok', { headers: corsHeaders });
}
try {
const { name } = await req.json();
const data = { message: `Hello ${name}!` };
return new Response(JSON.stringify(data), {
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
status: 200,
});
} catch (error) {
// Include CORS headers in error responses too!
return new Response(JSON.stringify({ error: error.message }), {
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
status: 400,
});
}
});
Critical points:
- The OPTIONS handler must come first in your function
- Include CORS headers in all responses (success AND error)
- List all headers your client sends in
Access-Control-Allow-Headers
Solution 2: Use corsproxy.io (When You Can’t Modify the Function)
If you’re calling someone else’s Edge Function or can’t redeploy:
// Wrap the Supabase function URL with corsproxy.io
const functionUrl = 'https://project.supabase.co/functions/v1/their-function';
const proxyUrl = `https://corsproxy.io/?url=${encodeURIComponent(functionUrl)}`;
const response = await fetch(proxyUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${supabaseAnonKey}`,
},
body: JSON.stringify({ data: 'test' }),
});
const result = await response.json();
Common Edge Function CORS Mistakes
- OPTIONS handler not first:
// Wrong - parsing body before OPTIONS check
const body = await req.json(); // Fails for OPTIONS requests!
if (req.method === 'OPTIONS') { ... }
// Correct - OPTIONS check first
if (req.method === 'OPTIONS') {
return new Response('ok', { headers: corsHeaders });
}
const body = await req.json();
- Missing headers in error responses:
// Wrong - no CORS headers on error
catch (error) {
return new Response(error.message, { status: 500 });
}
// Correct - CORS headers on all responses
catch (error) {
return new Response(error.message, {
headers: corsHeaders,
status: 500
});
}
- Missing required headers:
// Missing 'apikey' header that Supabase client sends
'Access-Control-Allow-Headers': 'authorization, content-type'
// Include all headers Supabase client uses
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type'
Fix REST API CORS Errors
Supabase’s REST API (powered by PostgREST) automatically includes CORS headers, so errors here usually indicate a different issue.
Common Causes
- Using wrong Supabase URL:
// Wrong URL format
const supabase = createClient('https://project.supabase.co:3000', key);
// Correct URL (no port)
const supabase = createClient('https://project.supabase.co', key);
-
CAPTCHA increasing JWT size: Some users have reported that enabling CAPTCHA can inflate the JWT token size, causing request failures that manifest as CORS errors.
-
Browser caching old CORS errors:
// Try in incognito mode or clear browser cache
// Chrome: DevTools → Network → Disable cache
- Row Level Security (RLS) returning 403: When RLS blocks a request, it may appear as a CORS error:
// Check your RLS policies
// A 403 Forbidden can sometimes show as CORS blocked
Solution: Use corsproxy.io for Third-Party Supabase Projects
If you’re accessing a Supabase project you don’t control:
import { createClient } from '@supabase/supabase-js';
// Create a custom fetch that routes through corsproxy.io
const customFetch = (url: string, options?: RequestInit) => {
const proxiedUrl = `https://corsproxy.io/?url=${encodeURIComponent(url)}`;
return fetch(proxiedUrl, options);
};
// Use custom fetch with Supabase client
const supabase = createClient(
'https://external-project.supabase.co',
'public-anon-key',
{
global: {
fetch: customFetch,
},
}
);
// Now all requests go through corsproxy.io
const { data } = await supabase.from('public_table').select('*');
Fix Auth CORS Errors
Supabase Auth (GoTrue) usually handles CORS correctly, but issues can occur in specific scenarios.
Local Development CORS Errors
Problem:
Cross-Origin Request Blocked: The Same Origin Policy disallows reading
the remote resource at http://localhost:54323/auth/v1/signup.
(Reason: CORS header 'Access-Control-Allow-Origin' missing).
Solution 1: Check your Supabase URL configuration:
// For local development with Supabase CLI
const supabase = createClient(
'http://localhost:54321', // API URL, not Auth URL
'your-anon-key'
);
Solution 2: Verify supabase start is running correctly:
# Stop and restart Supabase
supabase stop
supabase start
# Check the output for correct URLs
# API URL: http://localhost:54321
# Auth URL: http://localhost:54323 (internal, don't use directly)
Production Auth CORS Errors
If you’re getting CORS errors with Supabase Auth in production:
-
Check Site URL in Supabase Dashboard:
- Go to Authentication → URL Configuration
- Ensure your site URL matches your actual domain
-
Add Redirect URLs:
- Add all URLs your app uses (including localhost for dev)
- Include both http and https variants if needed
-
Use corsproxy.io as a temporary workaround:
// For auth endpoints you can't control
const authUrl = 'https://project.supabase.co/auth/v1/token?grant_type=password';
const proxyUrl = `https://corsproxy.io/?url=${encodeURIComponent(authUrl)}`;
const response = await fetch(proxyUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'apikey': supabaseAnonKey,
},
body: JSON.stringify({
email: 'user@example.com',
password: 'password123',
}),
});
Fix Storage CORS Errors
Supabase Storage CORS errors typically occur during file uploads or when accessing files directly.
Upload CORS Errors
Problem:
Access to fetch at 'https://project.supabase.co/storage/v1/object/bucket/file'
has been blocked by CORS policy
Solution 1: Check RLS Policies:
-- Ensure you have INSERT permission for uploads
CREATE POLICY "Allow uploads"
ON storage.objects FOR INSERT
TO authenticated
WITH CHECK (bucket_id = 'your-bucket');
-- For upsert, you also need SELECT and UPDATE
CREATE POLICY "Allow updates"
ON storage.objects FOR UPDATE
TO authenticated
USING (bucket_id = 'your-bucket');
Solution 2: Use the Supabase client properly:
// Use the Supabase client - handles auth automatically
const { data, error } = await supabase.storage
.from('bucket')
.upload('path/file.png', file);
// Don't construct storage URLs manually unless necessary
Downloading/Displaying Images with CORS
If you need to load Supabase Storage images with crossorigin attribute:
// Get a signed URL that includes proper headers
const { data } = await supabase.storage
.from('bucket')
.createSignedUrl('path/image.png', 3600);
// Use in img tag
const img = document.createElement('img');
img.crossOrigin = 'anonymous';
img.src = data.signedUrl;
Or use corsproxy.io for public bucket images:
const imageUrl = 'https://project.supabase.co/storage/v1/object/public/bucket/image.png';
const proxiedUrl = `https://corsproxy.io/?url=${encodeURIComponent(imageUrl)}`;
// Now you can use it with crossOrigin
const img = new Image();
img.crossOrigin = 'anonymous';
img.src = proxiedUrl;
Fix Realtime WebSocket CORS Errors
Supabase Realtime uses WebSockets, which have different CORS behavior than HTTP requests.
Common Issues
- Version-specific bugs: Version 2.49.8 of
@supabase/supabase-jshad WebSocket issues in local development.
# Downgrade if experiencing issues
npm install @supabase/supabase-js@2.49.7
-
Firefox WebSocket cleanup: Firefox can fail to properly clean up WebSocket connections.
-
Safari strictness: Safari is particularly strict with CORS policies.
Solution: Proper Realtime Setup
import { createClient } from '@supabase/supabase-js';
const supabase = createClient(supabaseUrl, supabaseKey, {
realtime: {
params: {
eventsPerSecond: 10,
},
},
});
// Subscribe to changes
const channel = supabase
.channel('table-changes')
.on(
'postgres_changes',
{ event: '*', schema: 'public', table: 'messages' },
(payload) => console.log('Change:', payload)
)
.subscribe((status) => {
if (status === 'SUBSCRIBED') {
console.log('Connected!');
}
});
Firewall/Network Issues
If WebSocket connections fail, check:
- Firewall isn’t blocking WebSocket traffic (port 443)
- VPN isn’t interfering with connections
- Corporate proxy allows WebSocket upgrades
Fix Self-Hosted Supabase CORS Errors
Self-hosted Supabase installations have unique CORS challenges.
Common Issues
- Incorrect Supabase URL in Studio: The self-hosted Supabase Studio may display an incorrect URL.
// URL shown in Studio might be wrong
const supabase = createClient('http://kong:8000', key);
// Use your actual Docker/server URL
const supabase = createClient('http://localhost:8000', key);
// Or your public domain
const supabase = createClient('https://api.yourdomain.com', key);
- Kong API Gateway configuration: Check your Kong configuration includes proper CORS headers:
# In your Kong configuration
plugins:
- name: cors
config:
origins:
- '*'
headers:
- Authorization
- Content-Type
- apikey
- x-client-info
methods:
- GET
- POST
- PUT
- PATCH
- DELETE
- OPTIONS
- Nginx reverse proxy: If using Nginx in front of Supabase:
location / {
# CORS headers
add_header 'Access-Control-Allow-Origin' '*' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always;
add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type, apikey, x-client-info' always;
# Handle preflight
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type, apikey, x-client-info';
add_header 'Access-Control-Max-Age' 86400;
return 204;
}
proxy_pass http://kong:8000;
}
When to Use corsproxy.io vs. Native Fixes
Use Native Supabase Fixes When:
- You control the Supabase project
- You can modify Edge Function code
- You’re building for production
- You need optimal performance
- You’re handling sensitive data
Use corsproxy.io When:
- You need a quick fix during development
- You’re accessing a third-party Supabase project
- You’re prototyping and don’t want to set up CORS yet
- You’re debugging to confirm it’s a CORS issue
- You can’t redeploy Edge Functions
- You’re working with a legacy project you can’t modify
Example: Fallback Pattern
async function fetchFromSupabase(endpoint: string, options: RequestInit) {
const supabaseUrl = `https://project.supabase.co${endpoint}`;
try {
// Try direct request first
const response = await fetch(supabaseUrl, options);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return await response.json();
} catch (error) {
// Fall back to corsproxy.io if CORS blocked
console.warn('Direct request failed, using proxy');
const proxyUrl = `https://corsproxy.io/?url=${encodeURIComponent(supabaseUrl)}`;
const response = await fetch(proxyUrl, options);
return await response.json();
}
}
Troubleshooting Checklist
Run through this checklist when debugging Supabase CORS errors:
General Checks
- Correct Supabase URL? No port numbers, correct project ID
- Anon key correct? Not the service role key in frontend
- Browser cache cleared? Try incognito mode
- Browser extensions disabled? Some CORS extensions cause issues
- Using latest Supabase client? Check for known version bugs
Edge Functions
- OPTIONS handler first? Before any
req.json()calls - CORS headers on all responses? Including error responses
- All required headers listed?
authorization, x-client-info, apikey, content-type - Function deployed? Check
supabase functions list
REST API
- RLS policies correct? 403 errors can appear as CORS
- Table is public or user authenticated? Check your policies
- CAPTCHA enabled? Can cause large JWT issues
Auth
- Site URL configured? In Dashboard → Authentication → URL Configuration
- Redirect URLs added? Including localhost for development
- Using correct auth URL? Don’t use the internal
:54323port directly
Storage
- Bucket policies set? Need INSERT for upload, SELECT for download
- Using Supabase client? Not constructing URLs manually
- Signed URLs for private buckets? Use
createSignedUrl()
Self-Hosted
- Kong CORS plugin configured? Check your docker-compose
- Nginx headers added? If using reverse proxy
- Correct internal vs external URLs? Studio may show wrong URL
Conclusion
Supabase CORS errors can be frustrating, but they’re almost always fixable once you understand which component is causing the issue:
- Edge Functions - Add CORS headers manually, OPTIONS handler first
- REST API - Usually automatic, check URL and RLS policies
- Auth - Configure Site URL and Redirect URLs in dashboard
- Storage - Set proper bucket policies and use Supabase client
- Realtime - Check library version and network/firewall settings
- Self-Hosted - Configure Kong or Nginx proxy properly
When you need a quick fix or are accessing Supabase projects you don’t control, corsproxy.io provides an instant solution:
const url = 'https://project.supabase.co/functions/v1/my-function';
const proxied = `https://corsproxy.io/?url=${encodeURIComponent(url)}`;
const response = await fetch(proxied, options);
Visit corsproxy.io for instant CORS fixes—free and no sign-up required.
Related Resources:
Community Discussions: