CORS Errors Explained: Why Your Frontend Can't Reach Your API
'Blocked by CORS policy: No Access-Control-Allow-Origin header' is one of the most common errors in AI-built apps. What CORS is, why the browser enforces it, how to fix it properly on the server, why the quick fixes are dangerous, and when you don't need CORS at all.
Your frontend calls your API. The request fails, and the browser console shows:
Access to fetch at 'https://api.example.com/plants' from origin
'https://example.com' has been blocked by CORS policy: No
'Access-Control-Allow-Origin' header is present on the requested resource.
The same URL works fine when you open it directly, or call it with curl. It's one of the most common and most confusing errors in web development. Here's what's going on.
The rule behind it: same-origin policy
Browsers enforce a security rule called the same-origin policy: JavaScript on one website can't freely read responses from a different website.
Without it, any page you visited could silently make requests to your bank, your email, or your company's internal tools — with your cookies attached — and read the results.
An origin is the combination of scheme + host + port:
| URL | Same origin as https://example.com? |
|---|---|
https://example.com/about |
✅ Yes (path doesn't matter) |
http://example.com |
❌ Different scheme |
https://api.example.com |
❌ Different host (subdomains count) |
https://example.com:8080 |
❌ Different port |
That last row is why CORS errors are so common in development: localhost:5173 (your frontend) and localhost:3000 (your API) are different origins. (What Is an IP Address and a Port?.)
What CORS is
CORS (Cross-Origin Resource Sharing) is the way a server says "it's OK — I allow that other origin to read my responses."
It does so with response headers:
Access-Control-Allow-Origin: https://example.com
When the browser sees that header matching the page's origin, it lets the JavaScript read the response. No header, or the wrong origin, and the browser blocks it.
Two things follow from this:
- CORS errors are fixed on the server (the API), not in the frontend. Nothing you put in the
fetchcall can grant permission. - CORS is enforced by the browser only. That's why
curl, Postman, and server-to-server calls work: they don't enforce it. CORS is not a way to protect your API from non-browser clients.
Preflight requests
For requests that aren't "simple" — using PUT or DELETE, sending JSON, or adding an Authorization header — the browser first sends an OPTIONS request asking permission. This is the preflight. The server must answer it with headers like:
Access-Control-Allow-Origin: https://example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
In your browser's Network tab, you'll see the OPTIONS request just before the real one. If it fails, the real request is never sent. (Browser Developer Tools for Beginners.)
Fixing it properly
On your API server, allow the specific origins that need access. With Express:
import cors from "cors"
app.use(cors({
origin: ["https://example.com", "http://localhost:5173"],
credentials: true, // only if you send cookies
}))
Most frameworks have an equivalent setting or middleware. Keep the list of allowed origins in configuration, so development and production can differ. (What Is an Environment Variable?.)
If you send cookies with cross-origin requests, you also need credentials: "include" on the fetch, and the server must name the exact origin — * isn't allowed with credentials.
The dangerous "fixes"
AI tools and forum answers often suggest these. Be careful:
Access-Control-Allow-Origin: *on an API with private data. This lets any website read responses. It's fine for truly public data (a public weather API). It's wrong for anything tied to a user's session or a private network.- Reflecting whatever origin the request sends. Code that copies the incoming
Originheader into the response, combined withcredentials: true, lets every website read your users' data. It's worse than*. - Browser extensions or flags that disable CORS. They make the error vanish on your machine only. Your users still get it.
- Public "CORS proxy" services. They send your traffic — possibly including tokens — through a stranger's server.
Often, you don't need CORS at all
The cleanest fix is frequently to make the frontend and API the same origin:
- Serve both from one domain, with the API under a path:
example.comandexample.com/api. Frameworks like Next.js do this naturally. (What Is Next.js?.) - In development, use your dev server's proxy. Vite, for example, can forward
/apirequests tolocalhost:3000, so the browser only ever talks to one origin:
// vite.config.js
export default {
server: { proxy: { "/api": "http://localhost:3000" } },
}
Same origin means no CORS configuration, no preflights, and simpler cookies.
When it's not really CORS
Sometimes the CORS message is a side-effect. If your API crashes with a 500 error, the error response often lacks the CORS headers — so the browser reports a CORS error instead of the real one. Check the server logs, and check the status code of the failed request. (HTTP Status Codes Explained.)
EasySpawn runs your frontend and backend together on your own domain, so they can share an origin — and CORS errors never come up in the first place. See how it works for AI-built apps or join the waitlist.
Related: What Is an API? · Anatomy of a URL · Why Does My App Work Locally but Not in Production?
Keep reading
I Leaked an API Key. What Now? A Step-by-Step Response
You pushed a .env file to GitHub, pasted a key in a screenshot, or found your secret key in your app's frontend code. What to do in the next ten minutes, the next hour, and the next day — revoke, rotate, check for abuse, clean up — and how to stop it happening again.
What Is XSS? Cross-Site Scripting Explained for Beginners
Cross-site scripting lets an attacker run their JavaScript in your users' browsers — stealing sessions, changing pages, acting as the user. How XSS works, the three types, why React mostly protects you, the escape hatches that don't, and the defences that matter.