How to Fix a CORS Error (With Real Header Examples)
How to fix a CORS error, step by step — why it's a server-side fix, the Access-Control-Allow-Origin header, and reading a real preflight response.
Try it now: HTTP Header Analyzer — Paste response headers from curl or DevTools and get them explained, with missing security headers, CSP weaknesses, cookie flags and cache contradictions flagged.
Step 1: Understand What a CORS Error Actually Means
The browser console throws a CORS error when JavaScript running on one origin tries to read the response from a request made to a different origin, and that other origin never said it was okay. The part almost everyone gets wrong is where the blocking actually happens: it is not the server refusing the request. In most cases the request reaches the server just fine — a GET runs, a POST inserts a row, an endpoint with side effects executes them — and the server sends a perfectly good response back. The browser then looks at that response, checks whether the server explicitly granted permission for the calling page's origin to read it, and if that permission is missing, it throws the response away before your code ever sees it.
This is the browser's same-origin policy doing its job, and it is enforced entirely client-side. Two origins count as different if the scheme, host, or port differs — https://myapp.com and https://api.myapp.com are different origins even though they share a parent domain, and http://localhost:3000 and http://localhost:8080 are different origins too, which is why CORS errors show up so often in local development. Understanding this distinction matters because it points to the actual fix: since the browser is the one enforcing the block, and it is enforcing it based on what the server said, the fix has to change what the server says — not anything on the client.
Step 2: Read the Actual Error Message
The exact wording varies slightly by browser, but a Chrome or Edge console typically shows something close to this:
Access to fetch at 'https://api.example.com/data' from origin 'https://myapp.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 phrases it a little differently — “Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at...” — but the diagnosis is identical either way: the response arrived, and the browser is refusing to hand it to your script because the Access-Control-Allow-Originheader the response needed was not present. That last sentence in Chrome's message, suggesting no-corsmode, is worth ignoring for anything but static assets — it produces an “opaque” response your code cannot read the body, status, or headers of, which is rarely what an API call actually needs.
Step 3: Stop Trying to Fix It in the Frontend
The instinct a lot of people have at this point is to search for a way to “disable CORS” from the client — a fetch option, an Axios flag, a browser extension. None of that is a real fix, and it is worth being direct about why. CORS is an opt-in mechanism enforced by the browser and controlled entirely by the server's response headers. There is no request header, fetch config, or client-side trick that makes a browser trust a response the server never authorized, because the whole point of the mechanism is that the client cannot grant itself that permission.
A CORS-disabling browser extension, or Chrome launched with --disable-web-security, does appear to make the error go away — but only in that one browser, on that one machine, for the one person who installed it. Every other visitor to the app, and the app in production, is completely unaffected and still broken. If the API is meant to be called from a browser at all, the header has to be added on the server. There is no shortcut around that.
Step 4: Add the Access-Control-Allow-Origin Header on the Server
The fix is one response header: Access-Control-Allow-Origin, set to either the exact origin that is allowed to read the response, or * to allow any origin. Use the exact origin whenever the API is not fully public, and reach for * only on a public, read-only API that never receives cookies or an Authorization header on a credentialed request (more on why in Step 6).
Access-Control-Allow-Origin: https://myapp.com
Here is that header being added with minimal Express middleware, without pulling in the cors package:
app.use((req, res, next) => {
res.setHeader("Access-Control-Allow-Origin", "https://myapp.com");
res.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
next();
});Or, more commonly in practice, with the cors package, which handles the preflight response from Step 5 automatically:
const cors = require("cors");
app.use(
cors({
origin: "https://myapp.com",
methods: ["GET", "POST", "PUT", "DELETE"],
allowedHeaders: ["Content-Type", "Authorization"],
}),
);Once that header is present on the actual response, the browser compares it against the calling page's origin, finds a match, and lets your JavaScript read the response. The request itself never changes — only whether the browser is willing to hand the result back.
Step 5: Handle the CORS Preflight Request
For a “simple” request — a plain GET, or a POST with a body type of application/x-www-form-urlencoded, multipart/form-data, or text/plain and no custom headers — the browser sends the real request straight away and just checks the response headers as described above. But a “non-simple” cross-origin request triggers something extra first: a CORS preflight request.
A request needs a preflight if it uses a method other than GET, POST, or HEAD; sends a custom header such as Authorization; or sends a POST body with Content-Type: application/json — which covers the overwhelming majority of API calls a modern frontend makes. Before sending that real request, the browser first sends an OPTIONS request to the same URL, asking the server whether the real request is allowed. The server has to answer that OPTIONS request with the right headers, or the browser cancels the real request and never sends it at all — this is a second, earlier point where things commonly break, separate from the response header on the actual GET or POST.
HTTP/1.1 204 No Content Access-Control-Allow-Origin: https://myapp.com Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS Access-Control-Allow-Headers: Content-Type, Authorization Access-Control-Max-Age: 86400
Access-Control-Allow-Methods has to list the method the real request will use, Access-Control-Allow-Headers has to list every custom header it will send, and Access-Control-Allow-Origin has to match just like it does on the real response. Access-Control-Max-Ageis optional, and just tells the browser how long, in seconds, it can cache that preflight answer before asking again. Frameworks like Express's corspackage, or an API gateway's built-in CORS config, generate this OPTIONS response automatically once configured — a common cause of an ongoing CORS error is a route handler that only exists for GET and POST, so the server has nothing set up to answer OPTIONS with at all, and it falls through to a 404 or 405 instead.
Step 6: Handle Credentialed Requests Correctly
If the request sends cookies, or uses credentials: "include" in a fetch call, or otherwise authenticates with something the browser holds, it is a credentialed request — and that changes the rules. When a response includes Access-Control-Allow-Credentials: true, Access-Control-Allow-Origin is not allowed to be * at all; the browser rejects the response outright if it sees that combination. It has to be one explicit origin.
Access-Control-Allow-Origin: https://myapp.com Access-Control-Allow-Credentials: true
This is a deliberate safety rail, not an arbitrary restriction: a wildcard origin combined with credentials would let any site on the internet make an authenticated request to the API using a logged-in user's cookies and read the result, which is exactly the cross-site request forgery scenario CORS exists to prevent. If the frontend sends credentials and the server responds with *, the fix is not to remove credentials — it is to change the server's Access-Control-Allow-Origin to the exact calling origin.
Step 7: Diagnose It by Reading the Actual Response Headers
Every step above depends on one specific header being present, on one specific response, with one specific value — and the console error text alone does not distinguish between “the header is completely missing,” “the header has the wrong origin,” and “the preflight OPTIONS request never got a CORS header at all.” Guessing which one it is wastes time; reading the response directly settles it in a few seconds. Pull the response up in your browser's Network tab, or run the request through curl -i, and paste the raw headers into GenKitLab's HTTP Header Analyzer. It explains every header, flags a missing or mismatched Access-Control-Allow-Origin, and — since it runs entirely client-side — works just as well on an authenticated or internal endpoint that a hosted “check my API” site could never reach in the first place.
The distinction to check for, in order: does the OPTIONS preflight response (if one happens at all) include Access-Control-Allow-Origin, -Allow-Methods, and -Allow-Headers? Does the real response include Access-Control-Allow-Origin, and does its value match the calling page's origin exactly — scheme, host, and port? And if credentials are involved, is Access-Control-Allow-Credentials: truepaired with an explicit origin rather than a wildcard? Answering those three questions from the actual headers, rather than from the error text, is what turns a CORS error from a guessing game into a one-line server fix. CORS headers are one narrow slice of a response's security posture — for the broader picture, including content security policy, see the guide to the CSP header, which the same analyzer checks alongside CORS.
Frequently asked questions
›How do I fix a CORS error?
Add an Access-Control-Allow-Origin header to the server's response, set to either the calling origin or * for a fully public, non-credentialed API. If the request is non-simple — a custom header, a method other than GET/POST/HEAD, or a JSON body — the server also needs to answer the browser's automatic OPTIONS preflight request with Access-Control-Allow-Origin, -Allow-Methods and -Allow-Headers. There is no client-side fix for a real CORS error; it has to be resolved on the server that owns the response.
›What does a CORS error actually mean?
It means the browser received a response to a cross-origin request but refused to let JavaScript read it, because the response didn't include a header explicitly granting permission to the calling page's origin. The request usually still reached the server and ran normally — it's the browser blocking access to the result, not the server rejecting the request.
›What does 'No Access-Control-Allow-Origin header is present' mean?
It means exactly what it says: the response the browser received had no Access-Control-Allow-Origin header at all, so the browser has no basis to trust the calling page's origin and discards the response. The fix is adding that header on the server, not retrying differently from the client.
›Can I fix a CORS error from the frontend?
No. A fetch option, an Axios setting, or a browser CORS-disabling extension does not change what the server sends, and CORS is enforced based entirely on the server's response headers. An extension only silences the error in your own browser — it does nothing for any other user hitting the same API, so it isn't a fix at all, just a local workaround.
›What is a CORS preflight request?
For a non-simple cross-origin request — a custom header, a method other than GET/POST/HEAD, or a JSON content type in most cases — the browser first sends an automatic OPTIONS request to check whether the real request is allowed. The server must respond to that OPTIONS request with Access-Control-Allow-Origin, -Allow-Methods and -Allow-Headers before the browser sends the actual GET, POST, PUT or DELETE at all.
›Why can't Access-Control-Allow-Origin be * with credentials?
Because a wildcard origin combined with Access-Control-Allow-Credentials: true would let any site on the internet make an authenticated request using a logged-in user's cookies and read the response — exactly the cross-site attack CORS exists to prevent. Browsers reject that combination outright, so a credentialed request needs Access-Control-Allow-Origin set to one explicit origin instead.
›How do I fix a CORS error in a React app?
The same way as any frontend: nothing in the React app itself can fix it, because the block happens based on the response headers the API server sends. Add Access-Control-Allow-Origin (and, if the request is non-simple, a correct preflight response) on the API server React is calling. If that server is one you don't control, ask the maintainer to add it, or route the request through a backend you do control.
Last updated