How to fix 'No Access-Control-Allow-Origin header is present'
Read the exact CORS error, find out whose server has to change, and pick the right fix: configure the API, add a backend, use a dev proxy or a CORS proxy.
It’s the most searched CORS error there is. Your fetch call fails, the Network panel shows a request that may even have returned 200, and the console says the response was blocked because no ‘Access-Control-Allow-Origin’ header is present on the requested resource. This guide shows how to read that message, how to decide which fix applies to you, and which popular “fixes” you should never ship.
Reading the error message
In Chrome the full message looks like this:
Access to fetch at 'https://api.example.com/v1/items' from origin 'https://app.example.com'
has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the
requested resource. If an opaque response serves your needs, set the request's mode to
'no-cors' to fetch the resource with CORS disabled.
Firefox says the same thing differently:
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource
at https://api.example.com/v1/items. (Reason: CORS header 'Access-Control-Allow-Origin' missing).
Status code: 200.
Three facts are hiding in there:
- The two origins. The page is on
https://app.example.comand the API is onhttps://api.example.com. Different subdomains, ports or schemes all count as different origins. - The request was sent. CORS rarely stops a request from reaching the server. The server processed it and replied; the browser refused to let your JavaScript read the reply. That’s why server logs show a successful request while your app shows a failure.
- The server didn’t opt in. The response simply had no
Access-Control-Allow-Originheader. The fix is on whichever server sent that response, not in yourfetchcall.
If the message instead mentions a preflight, the failing response is the OPTIONS request the browser sent first. The causes are a little different; see CORS preflight requests explained.
Why the browser blocks the response
The same-origin policy stops a page from reading responses from another origin, because those responses may contain data meant only for the logged-in user. A malicious page could otherwise call your bank’s API with your cookies and read the results. CORS is the mechanism a server uses to say “this origin may read my responses.” No header, no permission. For the full background, read What is CORS?.
The key point: only the server that sent the response can grant permission. There is no client-side code that makes a browser accept a response the server didn’t approve.
Fix 1: configure the API to send the header
If you own the API, this is the correct fix. Add Access-Control-Allow-Origin to its responses, naming the origin of your frontend:
Access-Control-Allow-Origin: https://app.example.com
Vary: Origin
Most frameworks have a CORS middleware that handles this, including the preflight. In Express, for example:
import cors from 'cors';
app.use(
cors({
origin: ['https://app.example.com', 'http://localhost:5173'],
}),
);
A few rules keep this safe:
- Allow-list origins rather than echoing whatever
Originyou receive. Reflecting any origin is equivalent to*and, combined with credentials, lets every site on the internet read your users’ data. - Use
*only for truly public, credential-free data. It can’t be combined with cookies at all; see CORS with credentials. - Send
Vary: Originwhenever the header’s value depends on the request, so caches don’t serve one origin’s response to another. - Make sure errors carry the header too. A
500from a crashed handler often skips the middleware, and the browser reports it as a CORS error, hiding the real problem.
Fix 2: call the API from your own backend
When you don’t own the API, or the API isn’t meant to be called from browsers, move the call to a server you control. Your frontend calls your backend on the same origin, and your backend calls the API. Servers aren’t subject to CORS at all.
This “backend for frontend” pattern has a second benefit: secret API keys stay on the server. If the API needs a private key, this is the only safe design. See why you can’t hide an API key in frontend code.
Framework-specific guides cover the details: Next.js, React and Vite, Vue and Nuxt, and SvelteKit and Astro.
Fix 3: a dev server proxy, for local development only
Dev servers like Vite and webpack can forward requests under a path such as /api to another host. The browser sees a same-origin request to localhost, so CORS never comes up:
// vite.config.js
export default {
server: {
proxy: {
'/api': {
target: 'https://api.example.com',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, ''),
},
},
},
};
This is excellent for development and useless in production: a static build has no dev server, so the /api path simply doesn’t exist once deployed. Plan the production fix before you rely on it.
Fix 4: a CORS proxy
A CORS proxy sits between your page and the API. It forwards your request and returns the response with the CORS headers the API didn’t send. It’s the right tool when you need to call a third-party API from the browser and running your own backend would be overkill.
Choose one carefully. An open proxy that forwards anything for anyone can be abused, can see your traffic, and can disappear without notice. Look for a proxy that ties requests to a key, restricts which sites may use that key, and keeps upstream credentials off the client.
What not to do
Don’t use mode: 'no-cors'
The console suggests it, and it does make the error go away. It does so by giving you an opaque response: response.status is 0, response.ok is false, and the body and headers are inaccessible. It’s meant for fire-and-forget requests and service worker caching, not for reading data.
const response = await fetch(url, { mode: 'no-cors' });
console.log(response.type); // "opaque"
await response.json(); // throws: there is no body you can read
no-cors also restricts the request to simple methods and headers, so a JSON POST with an Authorization header can’t even be expressed.
Don’t disable browser security
Launching Chrome with --disable-web-security or installing an “allow CORS” extension only changes your browser. Your users still see the error, and you lose a protection that keeps your own sessions safe while you browse.
Don’t “add the header” in your frontend
Setting Access-Control-Allow-Origin on the request does nothing. It’s a response header, and adding it to the request can make things worse by turning a simple request into a preflighted one.
Fixing it with ProxifyEdge
When the API isn’t yours and a backend isn’t worth building, send the request through ProxifyEdge:
const target = 'https://api.example.com/v1/items';
const response = await fetch(`https://api.proxifyedge.com/proxy?url=${encodeURIComponent(target)}`, {
headers: { 'X-API-Key': 'pk_your_public_key' },
});
The response comes back with Access-Control-Allow-Origin set for the origins your key allows. Live keys answer only the origins you list, so the key is safe to ship in a browser bundle; test keys answer any origin while you develop. If the API needs a secret token, store it in the secrets vault rather than in your JavaScript. Try it in the playground or read the quick start.
Summary
- The error means the server’s response didn’t include
Access-Control-Allow-Origin. The request was usually sent; only reading the response was blocked. - Only the server that sent the response can fix it. Nothing in your
fetchcall can. - Own the API? Add the header with an origin allow-list and
Vary: Origin. - Don’t own it? Call it from your backend, or through a CORS proxy that locks keys to your origins.
- Dev proxies fix local development only.
mode: 'no-cors'and disabled browser security fix nothing for your users.