Skip to content
Happy Programming Guide
Start learning
Web Development

What Is a CORS Error? Frontend or Backend Problem?

CORS errors come from the browser, are fixed on the server, and cannot be fixed in the frontend code. What the error means, why it exists, and how to read it.

Padlocks and keys laid out on a table

A CORS error means the browser blocked your JavaScript from reading a response from a different origin, because that server did not say it was allowed. It is enforced by the browser, but it is fixed on the server — the one you are calling — by sending the right headers. No amount of frontend code can make it go away, which is why it feels so unfair.

What an origin is#

An origin is scheme plus host plus port. These are all different origins:

Output
http://localhost:3000
http://localhost:8080        different port
https://localhost:3000       different scheme
https://api.example.com      different host

Your frontend at localhost:3000 calling an API at localhost:8080 is a cross-origin request. That is the single most common way developers first meet CORS.

Why the browser blocks it#

Browsers send your cookies with every request to a site, including requests made by JavaScript on some other site. Without a rule, any page you visit could call your bank’s API with your cookies and read the response. The same-origin policy is that rule: JavaScript may only read responses from its own origin.

CORS — Cross-Origin Resource Sharing — is the mechanism for a server to relax that rule deliberately. The server includes a header saying which origins may read its responses, and the browser honours it.

Reading the error#

Output
Access to fetch at 'https://api.example.com/data' from origin 'http://localhost:3000'
has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present
on the requested resource.

Take it literally. The browser fetched the resource, looked at the response, found no permission header, and refused to hand the body to your code. The request reached the server. This is why you can see the response in the Network tab but your code sees an error.

Variants you will meet:

Message says Meaning
No ‘Access-Control-Allow-Origin’ header Server sent no CORS headers at all
The ‘Access-Control-Allow-Origin’ header has a value ‘X’ that is not equal to the supplied origin Server allows a different origin than yours
Response to preflight request doesn’t pass access control check The OPTIONS request failed; see below
Request header field authorization is not allowed Server did not list that header in Access-Control-Allow-Headers
credentials flag is true but Access-Control-Allow-Credentials is not ‘true’ Cookies or auth headers need an extra opt-in

Frontend or backend?#

Backend. Always. The fix is the server sending:

Output
Access-Control-Allow-Origin: http://localhost:3000

The frontend’s only involvement is that its origin has to match what the server allows. There is no fetch option, no header you can add from JavaScript, and no framework setting on the client side that bypasses it. Adding mode: 'no-cors' to fetch does not fix it either; it just hides the error and gives you an unreadable response.

If you own the server, add the header. Every backend framework has a one-line way to do it:

JavaScript
// Express
const cors = require('cors');
app.use(cors({ origin: 'http://localhost:3000' }));
Python
# Flask
from flask_cors import CORS
CORS(app, origins=["http://localhost:3000"])
Python
# FastAPI
from fastapi.middleware.cors import CORSMiddleware
app.add_middleware(CORSMiddleware, allow_origins=["http://localhost:3000"], allow_methods=["*"], allow_headers=["*"])

Preflight requests#

For anything beyond a simple GET or a form-style POST — a JSON body, a custom header, a PUT or DELETE — the browser first sends an OPTIONS request asking permission. That is the preflight. The server must answer it with the allowed methods and headers:

Output
Access-Control-Allow-Origin: http://localhost:3000
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 86400

A common failure: the server has CORS headers on the real endpoint, but the OPTIONS request hits an authentication check first and gets a 401. The preflight fails, so the real request never happens. CORS middleware has to run before authentication.

The Max-Age header tells the browser to remember the answer, so it does not preflight every single call.

Cookies and tokens#

If the frontend sends cookies or an Authorization header, two more things are needed. The client must opt in:

JavaScript
fetch(url, { credentials: 'include' });

And the server must respond with Access-Control-Allow-Credentials: true and a specific origin, not the wildcard. Missing either produces the credentials error in the table.

When the server is not yours#

You cannot make a third-party API send headers it does not send. The standard answer is a proxy: your own backend calls the API and your frontend calls your backend. Server-to-server requests are not subject to CORS, because there is no browser involved.

JavaScript
// Express: forward /api/weather to the real API
app.get('/api/weather', async (req, res) => {
  const upstream = await fetch('https://weather.example.com/today?key=' + process.env.KEY);
  res.json(await upstream.json());
});

This also keeps the API key out of the browser, which you wanted anyway.

For local development only, most frontend dev servers can proxy for you. Vite, Create React App and Angular each have a proxy setting that forwards /api to your backend, so the browser sees a single origin. That is a development convenience; production still needs real headers or a real proxy.

Diagnosing in two minutes#

  1. Open the browser’s Network tab and find the failed request.
  2. Check whether there is an OPTIONS request before it, and whether that succeeded.
  3. Look at the response headers for Access-Control-Allow-Origin. Missing, wildcard, or the wrong origin tells you which case you have.
  4. Reproduce from the command line to confirm it is the server, not the browser:
Terminal
curl -i -X OPTIONS https://api.example.com/data \
  -H "Origin: http://localhost:3000" \
  -H "Access-Control-Request-Method: POST"

If the headers are not in that output, the server is not sending them, and no client-side change will help.

Questions people ask#

Why does the request work in Postman or curl?

Because they are not browsers. CORS is enforced only by browsers, on behalf of the user whose cookies are at stake.

Why do I get a CORS error when the server returns 500?

Error responses often skip the middleware that adds CORS headers, so the browser reports CORS rather than the real error. Check the server logs; the CORS message is hiding a crash.

Is CORS a security feature I could turn off?

It is a browser protection for users, not a setting on your site. You configure what your server allows; you cannot disable it for visitors.

Does it apply to images and scripts?

Not in the same way. Tags such as img and script can load cross-origin resources; what CORS restricts is JavaScript reading the response, which is what fetch and XMLHttpRequest do.

Where to go next#

Fixing CORS in React and Node, step by stepRead next

Keep reading

Keep going — pick your next guide

The fastest way to improve is to read one guide, then build the thing it describes. Start with the basics, or jump straight to a project.

Ask a question or share what worked

Your email address will not be published. Required fields are marked *