Fix CORS Errors When Calling an API from Lovable (or Any Frontend)
Quick answer: "Blocked by CORS policy" when your Lovable or Bolt app calls an API? Learn why browsers block it and how to fix it with a backend proxy or Supabase Edge Function.
The error
Access to fetch at 'https://api.example.com/data' from origin 'https://your-app.lovable.app' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
Why it happens
Browsers block JavaScript from reading responses from another origin unless that server explicitly allows it with CORS headers. It isn't a bug in your code. The API server decides. Common cases in AI-built apps:
- Calling a third-party API that doesn't allow browser requests (many expect server-side calls with a secret key)
- Your own Edge Function/backend doesn't return CORS headers, or doesn't handle the
OPTIONSpreflight - Sending custom headers (e.g.
Authorization), which triggers a preflight the server rejects
Fixes
1. Call the API from a server, not the browser (the right fix for third-party APIs)
Supabase Edge Function proxy (Deno):
// supabase/functions/proxy/index.ts
const corsHeaders = {
'Access-Control-Allow-Origin': '*', // or your exact app origin
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
};
Deno.serve(async (req) => {
if (req.method === 'OPTIONS') return new Response('ok', { headers: corsHeaders });
const res = await fetch('https://api.example.com/data', {
headers: { Authorization: `Bearer ${Deno.env.get('EXAMPLE_API_KEY')}` },
});
const data = await res.json();
return new Response(JSON.stringify(data), {
status: res.status,
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
});
});
Set the secret and deploy:
supabase secrets set EXAMPLE_API_KEY=your_key
supabase functions deploy proxy
Call it from the frontend:
const { data, error } = await supabase.functions.invoke('proxy');
invoke sends your user's session token automatically, which the function checks by default ("Verify JWT"). If you call the function with plain fetch instead, include the Authorization and apikey headers.
This also keeps your API key out of the browser.
2. If it's your own server, add CORS headers
Express:
import cors from 'cors';
app.use(cors({ origin: 'https://your-app.lovable.app' }));
Next.js Route Handler:
export async function OPTIONS() {
return new Response(null, { headers: {
'Access-Control-Allow-Origin': 'https://your-app.lovable.app',
'Access-Control-Allow-Methods': 'GET,POST,OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
}});
}
Add the same Access-Control-Allow-Origin header to your GET/POST responses too.
3. Things that don't work
mode: 'no-cors'gives you an opaque response you can't read.- Browser CORS extensions only "fix" it on your own machine.
- Adding
Access-Control-Allow-Originto your request headers does nothing, because it's a response header.
Prompt to paste into Lovable
Move this external API call into a Supabase Edge Function that handles OPTIONS preflight, returns CORS headers, and reads the API key from secrets. Call it with supabase.functions.invoke from the frontend.
FAQ
Why does it work in Postman/curl? CORS is enforced only by browsers.
Is Access-Control-Allow-Origin: * safe? For public, non-credentialed endpoints, yes. It can't be combined with cookies/credentials. Use your exact origin then.
Still stuck? Paste your exact error into the free Error Fixer to get a plain-English cause, the fix, and a prompt to paste back into your AI tool.