CORS Explained: Why Cross-Origin Requests Get Blocked and How to Fix It
Your API works fine in Postman and fails the moment the browser makes the same request from your frontend. That gap is almost always CORS, and the fix belongs on the server, not in the browser console.
Published July 6, 2026CORS — Cross-Origin Resource Sharing — is a relaxation of a much older browser rule called the same-origin policy. By default, a page loaded from https://app.example.com cannot read the response of a request made to https://api.example.com, even though both belong to you, because the two are different origins (different subdomain). The same-origin policy exists to stop a malicious page from silently reading data out of a banking site you happen to be logged into in another tab. CORS is the mechanism that lets a server explicitly say "I'll allow this specific other origin to read my responses."
What counts as a different origin
An origin is the combination of scheme, host, and port. https://api.example.com and http://api.example.com are different origins because the scheme differs. https://api.example.com and https://app.example.com are different origins because the host differs, even though they share a parent domain. https://example.com and https://example.com:8080 are different origins because the port differs. Any one of those three not matching triggers cross-origin rules.
The request still happens — the browser just hides the response
A common misconception is that CORS blocks the request from being sent. It doesn't. For a simple GET request, the browser sends it to the server, the server processes it and may even write to a database, and only then does the browser check the response headers and decide whether JavaScript is allowed to read the body. If the Access-Control-Allow-Origin header doesn't include the calling page's origin, the browser throws the response away before your code ever sees it — but the server already executed the request. This matters for anything with a side effect: a blocked CORS response does not mean the action didn't happen on the server.
Preflight requests
For requests that aren't "simple" — anything using methods other than GET/HEAD/POST, custom headers like Authorization, or a content type other than form-encoded — the browser sends an OPTIONS request first, before the real one, asking permission:
OPTIONS /api/orders HTTP/1.1
Origin: https://app.example.com
Access-Control-Request-Method: DELETE
Access-Control-Request-Headers: authorization
The server must respond to this preflight with headers confirming the method and headers are allowed:
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, POST, DELETE
Access-Control-Allow-Headers: authorization
Access-Control-Max-Age: 86400
Only if the preflight succeeds does the browser send the actual DELETE request. Access-Control-Max-Age tells the browser how long it can cache this preflight result, so it doesn't repeat the OPTIONS round trip on every single request — without it, every cross-origin DELETE or authenticated request pays for two round trips instead of one.
Why Access-Control-Allow-Origin: * often doesn't work
The wildcard looks like the easy fix, and it works for public, unauthenticated endpoints. It stops working the moment a request includes credentials — cookies, or an Authorization header combined with credentials: 'include' on the client. The CORS spec explicitly disallows combining a wildcard origin with credentialed requests, because "allow any origin to read this" and "allow any origin to read this using the user's session" are very different levels of exposure. If your API needs cookies or credentialed requests, you must echo back the specific requesting origin (validated against an allowlist) rather than using the wildcard.
Fixing it on the server, not around it
The correct fix is server-side: configure your API framework's CORS middleware with the exact list of origins allowed to call it, and let it handle preflight responses correctly. Developers sometimes route around CORS during development by using a proxy that makes the request appear same-origin, which is a legitimate development convenience but shouldn't become the production architecture — it just moves the problem rather than solving it, and a browser extension that disables CORS checking locally will hide bugs that reappear the instant a real user without that extension hits the same page.
For more detail on the header-level mechanics, the Mozilla Developer Network's CORS reference is the most complete and consistently maintained explanation of every header involved: developer.mozilla.org/en-US/docs/Web/HTTP/CORS.