An origin is more than a domain name
A page’s origin is the combination of its scheme, host, and port. A page served from http://localhost:3000 and an API at http://localhost:8000 have different origins because their ports differ. Changing from http to https also changes the origin, even when the host stays the same. These details matter during local development as much as they do after deployment.
Browsers apply the same-origin policy to protect data that a page might otherwise read from another origin. Cross-origin resource sharing, or CORS, is a way for the server receiving a request to tell the browser which origins may read its response. It is not a setting the calling page can grant itself. A fetch that works in a server-side script or command-line client may therefore fail in browser JavaScript.
Why a fetch can fail after the server responds
Consider a page at http://localhost:3000 calling an API at http://localhost:8000. If the API intends to let that page read a response, it can include Access-Control-Allow-Origin: http://localhost:3000 in its response headers. If the header is absent or names a different origin, the browser blocks JavaScript from accessing the response. Developer tools may still show that a request was sent and a response arrived; a CORS failure does not necessarily mean the server was unreachable.
For a public response that does not involve credentials, a server can instead use Access-Control-Allow-Origin: *. A server that reflects an allowed request origin rather than sending a fixed value should check it against an explicit allowlist. If the response varies by request Origin, adding Vary: Origin helps caches keep responses for different origins separate. CORS does not replace authentication or authorization: allowing a page to read a response is not the same as deciding whether a user may access its data.
When the browser sends a preflight
Some cross-origin requests need an extra check before the main request. For example, a fetch using the PUT method or sending a custom request header typically causes the browser to send an OPTIONS preflight request. That request identifies the intended method and headers. The server must answer the preflight successfully before the browser sends the main request.
A suitable preflight response includes Access-Control-Allow-Origin for the calling origin, Access-Control-Allow-Methods listing the permitted method, and Access-Control-Allow-Headers listing any requested headers the server permits. The API must also send an appropriate Access-Control-Allow-Origin header on the actual response. Handling OPTIONS alone is not enough. By contrast, a basic GET request might be sent without a preflight, but its response still needs CORS permission before JavaScript can read it.
When diagnosing a failure, inspect the network panel for an OPTIONS request and compare its request headers with the response headers. Check whether middleware, a redirect, or an error handler returned a response without the expected CORS headers. The browser’s console message is a useful starting point, but the request and response headers usually make the mismatch clearer.
Credentials need explicit permission
Cookies and other browser-managed credentials add another requirement. For a cross-origin fetch that should include cookies, the caller generally needs to set the fetch credentials option to include. The server must then send Access-Control-Allow-Credentials: true and name the permitted origin explicitly in Access-Control-Allow-Origin. The wildcard value * cannot be used for that credentialed response.
CORS permission does not guarantee that a cookie will be sent or stored. Cookie attributes and browser cookie rules still apply. Nor does setting credentials in fetch make an unauthorized API call valid: the server must continue to verify the user and the requested action. If browser code needs to read a non-safelisted response header, the server can name it in Access-Control-Expose-Headers; that header is separate from the headers allowed on the request.
A reliable way to troubleshoot
Start by writing down the page origin and API origin, including schemes and ports. Then check whether the browser sent a preflight, whether it succeeded, and whether the main response contains the required CORS headers. Repeat the check for error responses, since an API may configure CORS on successful responses but omit it when something goes wrong.
Make the fix on the API or a proxy you control, not by adding Access-Control-Allow-Origin to the fetch request. Avoid treating mode: "no-cors" as a workaround: it usually leaves JavaScript with an opaque response it cannot inspect. For local development, a same-origin development proxy can simplify requests; for a genuinely cross-origin deployment, configure the server to allow only the intended origins, methods, and headers.



