You build a frontend app. You build a backend API.
Both work fine on their own.
But once the frontend sends a request to the backend, the browser shows an error like this:
"Access to fetch at 'http://localhost:3000/messages' from origin 'http://localhost:5173' has been blocked by CORS policy [...]"
That's a CORS error.
And if you're new to web development, it can be quite confusing.
Because the backend might be working. The URL might be correct. The request might even reach the server.
Still, the browser blocks your frontend code from using the response.
So let's explore what CORS is, why it exists and how you typically fix those errors.
What Is CORS?
CORS stands for Cross-Origin Resource Sharing.
It's a browser security mechanism that controls whether JavaScript code from one origin may read responses from another origin.
In simpler words:
If your frontend app is loaded from one URL and it tries to fetch data from another URL, the browser checks whether that is allowed.
If the server does not explicitly allow it, the browser blocks your frontend code from accessing the response.
That's CORS.
What Is An Origin?
An origin is defined by three things:
- Protocol / scheme
- Domain / host
- Port
For example:
http://localhost:5173This origin consists of:
- Protocol:
http - Host:
localhost - Port:
5173
These are different origins:
http://localhost:5173
http://localhost:3000
https://localhost:5173
https://api.example.com
https://example.comEven if two URLs look very similar, a different protocol, domain or port means: different origin.
That's why CORS errors are so common during development.
Your frontend might run on localhost:5173 and your backend API on localhost:3000. For the browser, those are two different origins.
Why Does CORS Exist?
CORS exists because of the same-origin policy.
The same-origin policy is a browser security concept. It prevents JavaScript code from one website from freely reading data from another website.
Imagine this would be allowed without restrictions:
- You're logged into your online banking website
- You open some malicious website in another tab
- That malicious website sends requests to your banking website from your browser
- It reads the responses and sends your private data away
That would obviously be a huge problem.
Browsers therefore restrict what cross-origin JavaScript code can read.
CORS is the controlled exception to that rule.
It allows a server to say:
"Yes, this other origin may read my responses."
Or:
"No, this origin is not allowed."
Where Does CORS Apply?
CORS is mainly relevant for browser-based JavaScript requests, for example requests sent with fetch() or XMLHttpRequest.
It can also matter for some other browser features like fonts, images used in canvas, WebGL textures and similar cases.
But the most common case is simple:
A frontend app in the browser wants to call a backend API on a different origin.
Important: CORS is enforced by browsers.
If you send the same request with Postman, curl, a backend server or a mobile app, CORS does not block it.
That's why CORS is not a general API security mechanism. It's a browser security mechanism.
How CORS Works
When a browser sends a cross-origin request, it includes an Origin header.
For example:
Origin: http://localhost:5173The server can then respond with CORS headers.
The most important one is:
Access-Control-Allow-Origin: http://localhost:5173That response header tells the browser:
"Frontend code loaded from
http://localhost:5173may read this response."
If the header is missing, or if it contains a different origin, the browser blocks the response from your JavaScript code.
The request may still have reached the server. The server may even have sent a response.
But the browser does not expose that response to your frontend code.
Simple Requests And Preflight Requests
Some cross-origin requests are sent directly.
Others first trigger a so-called preflight request.
A preflight request is an automatic OPTIONS request sent by the browser before the actual request.
It basically asks the server:
"May I send this real request with this method and these headers?"
You often see preflight requests when your frontend uses:
- Methods like
PUT,PATCHorDELETE - Custom headers
- An
Authorizationheader - JSON requests with
Content-Type: application/json
A preflight request can look like this:
OPTIONS /messages HTTP/1.1
Origin: http://localhost:5173
Access-Control-Request-Method: PATCH
Access-Control-Request-Headers: Content-Type, AuthorizationThe server must answer with headers that allow the requested method and headers:
Access-Control-Allow-Origin: http://localhost:5173
Access-Control-Allow-Methods: GET, POST, PATCH, DELETE
Access-Control-Allow-Headers: Content-Type, AuthorizationIf that preflight response is missing or incorrect, the browser never sends the actual request.
So if you see an OPTIONS request in your network tab: That's not a bug. That's the browser checking CORS permissions.
Important CORS Headers
There are a couple of CORS headers you should know.
You won't always need all of them, but these are the most important ones.
Access-Control-Allow-Origin
This header defines which origin may read the response.
Example:
Access-Control-Allow-Origin: https://my-frontend.comYou can also use *:
Access-Control-Allow-Origin: *That means any origin may read the response.
This can be fine for public APIs where no credentials are involved. But don't blindly use it for everything.
And important: You can't use * together with credentials such as cookies.
Access-Control-Allow-Methods
This header tells the browser which HTTP methods are allowed for preflighted requests.
Example:
Access-Control-Allow-Methods: GET, POST, PATCH, DELETEIf your frontend sends a PATCH request but the server only allows GET, POST, the browser blocks it.
Access-Control-Allow-Headers
This header tells the browser which non-standard / non-simple request headers may be sent.
Example:
Access-Control-Allow-Headers: Content-Type, AuthorizationYou need this if your frontend sends JSON, auth tokens or custom headers.
Access-Control-Allow-Credentials
This header matters when cookies or other credentials are involved.
Example:
Access-Control-Allow-Credentials: trueOn the frontend, you also have to opt into credentials:
fetch('https://api.example.com/user', {
credentials: 'include',
})If you use credentials, the server must return a specific allowed origin. It may not use Access-Control-Allow-Origin: *.
Access-Control-Max-Age
This header lets the browser cache a successful preflight response for some time.
Example:
Access-Control-Max-Age: 86400That can reduce the number of preflight requests. Browsers may still apply their own limits, but it can help.
Access-Control-Expose-Headers
By default, frontend JavaScript can only read a small set of response headers.
If your API returns custom response headers that frontend code should read, expose them explicitly:
Access-Control-Expose-Headers: X-Total-CountVary: Origin
If your server dynamically allows different origins, you should also set:
Vary: OriginThis matters for caches.
It tells caches that the response can vary depending on the Origin request header. Without it, a shared cache might accidentally reuse a response with the wrong CORS header.
How To Fix CORS Errors
The most important rule is this:
CORS is fixed on the server, not in the frontend request.
This will not fix CORS:
fetch('http://localhost:3000/messages', {
headers: {
'Access-Control-Allow-Origin': '*',
},
})Those headers must be sent by the backend response.
A very simple backend response could include:
Access-Control-Allow-Origin: http://localhost:5173
Access-Control-Allow-Methods: GET, POST, PATCH, DELETE
Access-Control-Allow-Headers: Content-Type, AuthorizationAnd your backend must also handle OPTIONS requests if the browser sends preflight requests.
In development, frameworks or dev servers sometimes offer proxy features that avoid CORS by forwarding requests through the same origin. That's fine for development.
But in production, your real API should be configured with the correct CORS policy.
Common CORS Mistakes
There are a few mistakes that come up again and again.
Adding CORS Headers To The Frontend Request
As mentioned above: This does not work.
CORS headers like Access-Control-Allow-Origin are response headers. They must come from the server that owns the resource.
Thinking CORS Protects Your API From Everyone
CORS does not stop all requests.
It stops browser JavaScript from reading responses when the server did not allow it.
It does not stop Postman, curl, backend servers or attackers from sending requests directly.
So you still need real security:
- Authentication
- Authorization
- Input validation
- CSRF protection where needed
- Rate limiting where needed
- Proper secret handling
CORS is not a replacement for any of that.
Allowing Every Origin With Credentials
This is dangerous and often invalid.
If your API uses cookies or sessions, don't use Access-Control-Allow-Origin: *.
Use a strict allowlist of trusted origins instead.
Forgetting The Preflight Request
If your backend only handles GET and POST, but the browser first sends an OPTIONS request, your actual request may never happen.
Make sure your server answers preflight requests with the right CORS headers.
Returning CORS Headers Only For Successful Responses
CORS headers should usually also be included on error responses.
Otherwise, the browser may show a CORS error even though the real problem is a 401, 403 or 500 response.
That can make debugging much harder.
A Practical Checklist
If you run into a CORS error, check this:
- What is the frontend origin? Protocol, host and port.
- What is the backend origin?
- Does the backend response include
Access-Control-Allow-Origin? - If a preflight request is sent, does the backend handle
OPTIONS? - Are all required methods listed in
Access-Control-Allow-Methods? - Are all required headers listed in
Access-Control-Allow-Headers? - Are you using cookies or credentials? Then avoid
*and use an exact origin. - Are CORS headers also returned for error responses?
- Are you trying to fix CORS in frontend code? Move that logic to the backend.
In A Nutshell
CORS is a browser mechanism that controls whether JavaScript from one origin may read responses from another origin.
It exists because browsers need to protect users from malicious cross-origin data access.
To allow cross-origin requests, the backend must return the right CORS response headers.
And most importantly: CORS is useful, but it is not your complete API security system.
It controls browser access to responses. It does not replace authentication, authorization or proper backend security.
Learn More
These resources are great next steps if you want to go deeper:
