All posts
4 min read

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.

getting starteddebuggingsecuritybeginner

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:

  1. CORS errors are fixed on the server (the API), not in the frontend. Nothing you put in the fetch call can grant permission.
  2. 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 Origin header into the response, combined with credentials: 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.com and example.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 /api requests to localhost: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