Your API works when you test it in the browser address bar or in Postman. Then your web page on localhost tries to fetch the same address, and the console turns red with a CORS error. Nothing loads, and the message talks about origins and headers you never set.
CORS problems frustrate almost every developer during local development, but they are not random and they are not a browser bug. The browser is deliberately protecting the user. In this guide, you will learn what CORS is in simple terms, why localhost triggers it, and the correct ways to fix it without weakening security.
Quick Answer
A CORS error when fetching from localhost happens because your page origin, such as http://localhost:3000, is different from the API origin, and the API server did not send an Access-Control-Allow-Origin header allowing your origin. Fix it by enabling CORS on the API server with your localhost origin, using a development proxy in your frontend tool, or running the frontend and API on the same origin.
What CORS Means in Simple Words
CORS stands for Cross-Origin Resource Sharing. An origin is the combination of protocol, domain, and port. That means http://localhost:3000 and http://localhost:5000 are different origins because the port differs, and http://localhost:3000 and https://localhost:3000 are also different because the protocol differs.
Browsers follow the same-origin policy: a page may freely read responses only from its own origin. When JavaScript on one origin tries to read a response from another origin, the browser asks the server for permission using special response headers. If the permission header is missing or does not include your origin, the browser blocks your JavaScript from reading the response and reports a CORS error in the console.
Two details matter a lot. First, the request often still reaches the server — the server may even return a successful status — but the browser hides the response from your code. Second, tools like Postman and curl do not enforce CORS at all, because they are not browsers. That is exactly why the API can “work everywhere except in fetch” and still be a genuine CORS problem.
Why Localhost Triggers This So Often
Local development naturally splits your app across origins. A typical setup runs a React, Vue, or Vite frontend on port 3000 or 5173, while the backend runs on port 4000, 5000, or 8000. To the browser, that is already a cross-origin request, even though both services run on your own machine.
Other local triggers include opening your HTML file directly with a file:// address, which gives the page an opaque origin, and calling a public third-party API that simply does not offer CORS headers for browser use. In that last case, no frontend trick can grant permission the API owner has not given; you need a small backend of your own to call that API on the server side.
Some requests also trigger a preflight check. Before sending a request with JSON content, custom headers, or methods like PUT and DELETE, the browser first sends an automatic OPTIONS request to ask permission. If the server does not handle OPTIONS correctly, the real request is never sent, and the console shows a preflight failure instead.
How to Confirm It Is Really CORS
- Open the Console and read the full message. A true CORS error names your origin, the target URL, and the missing header or the preflight failure.
- Open the Network tab and find the fetch request. Check whether a response was returned and whether
Access-Control-Allow-Originappears in the response headers. - Look for an OPTIONS request just before the failed request. If the OPTIONS request fails or lacks CORS headers, you have a preflight problem.
- Test the same URL with curl or Postman. If it works there but not in the browser, that supports a CORS diagnosis rather than a server outage.
- Check for mixed content too. A page served over HTTPS calling an HTTP API can be blocked for a different security reason, so confirm the exact message before changing server code.
The Correct Fixes, Step by Step
Fix 1: Enable CORS on Your Own API Server
This is the proper long-term fix when you control the backend. The server must respond with a header that names your frontend origin:
Access-Control-Allow-Origin: http://localhost:3000
In Express for Node.js, the usual steps are:
- Install the CORS middleware with
npm install cors. - Import it near the top of your server file with
const cors = require("cors");or the module equivalent. - Apply it before your routes with
app.use(cors({ origin: "http://localhost:3000" })); - Restart the server and reload your page. Check the Network tab again to confirm the header is present.
Equivalent middleware and settings exist in every major framework, including Django, Laravel, Flask, Spring, and ASP.NET. The principle is identical: allow the specific frontend origin, allow the methods and headers your app actually uses, and handle the OPTIONS preflight. Avoid using the wildcard * together with credentials, because browsers reject that combination when cookies or authorization headers are involved.
Fix 2: Use a Development Proxy
When you do not want to change the backend during development, let your frontend dev server proxy API calls. Your browser code calls a same-origin path like /api/users, and the dev server forwards it to the backend on another port. Since the browser only ever talks to its own origin, no CORS check blocks the read.
In Vite, add a proxy entry for /api pointing to your backend target in the config file. In Create React App, a simple approach is the "proxy" field in package.json. Proxies are excellent for development, but remember they exist only in the dev server; your production setup needs a real solution such as server CORS headers or a reverse proxy.
Fix 3: Serve Frontend and API From the Same Origin
If your backend serves the built frontend files as well, the page and the API share one origin and CORS stops being an issue for your own app. This is common for small projects and for production deployments behind a reverse proxy, where a path like /api is routed to the backend while everything else serves the site.
Fix 4: Go Through Your Own Backend for Third-Party APIs
If a public API does not send CORS headers, do not try to defeat the browser. Create a small endpoint on your own server that calls the third-party API server-to-server, then returns the result to your frontend with your own CORS headers. Server-to-server calls are not subject to browser CORS rules, and this pattern also keeps API keys off the client, where anyone could read them.
If CORS confusion is mixed with promises resolving at the wrong time, read our related explanation of why fetch requests can return undefined in JavaScript so you can separate network permission problems from async handling mistakes.
Common Mistakes and Security Tips
- Disabling browser security: Launching Chrome with web security turned off may appear to work, but it only masks the problem on your machine and exposes you while browsing. Never treat it as a fix.
- Installing a CORS browser extension as the solution: Extensions help only your own browser. Every real user would still be blocked. Fix the server or the architecture instead.
- Allowing every origin in production: Setting
Access-Control-Allow-Origin: *on an API that uses credentials is both unsafe and, in browsers, ineffective for credentialed requests. List the exact origins you trust. - Forgetting headers in the preflight: If your request sends an
Authorizationheader or JSON content type, the server must also allow those headers in its preflight response, not just the origin. - Expecting frontend code to set the response header:
Access-Control-Allow-Originis a response header set by the server. Adding it to your fetch request headers does nothing useful and can trigger a preflight.
The authoritative reference is the MDN Web Docs complete guide to Cross-Origin Resource Sharing, which explains simple requests, preflighted requests, and credentialed requests with header examples for each case.
Conclusion
A CORS error from localhost means the browser asked the API server for permission to share its response with your page origin, and the server did not grant it. The fix belongs on the server or in your architecture, not in the browser: enable CORS with your exact localhost origin on an API you control, use a dev proxy while building, serve both parts from one origin, or route third-party calls through your own backend. Confirm the result in the Network tab by checking that the response carries the right headers, including for the OPTIONS preflight. Done properly, the fix is small, secure, and permanent — and it will keep working when you deploy, not just on your own machine.
Frequently Asked Questions
Why does the API work in Postman but fail in the browser?
Postman and curl do not enforce the browser same-origin policy, so they display the response directly. Browsers enforce CORS to protect users, so the same response is blocked from your JavaScript when the server omits the required permission headers.
Is localhost considered a secure origin for CORS?
Browsers treat localhost as a trustworthy context for many features, but it is still a distinct origin defined by protocol, host, and port. A different port or protocol means a different origin, so CORS rules still apply between local services.
What is a preflight request and why does it fail?
A preflight is an automatic OPTIONS request the browser sends before certain cross-origin requests, especially those with JSON bodies, custom headers, or methods like DELETE. It fails when the server does not answer OPTIONS with the allowed origin, methods, and headers.
Can I fix CORS purely in frontend code?
Only in development, by using your dev server proxy so the browser sees a same-origin call. For a direct cross-origin fetch, permission must come from the server response headers. There is no frontend header that can grant it.
Should I use Access-Control-Allow-Origin: * in production?
Only for a truly public API that uses no cookies or credentials and is safe for any website to read. For apps with logins or private data, list your exact production origins instead and enable credentials support correctly on the server.


Leave a Reply