When building modern web applications, you’ll almost certainly run into CORS errors. They can be confusing at first, especially when your frontend and backend live on different domains.
In this post, I’ll explain what CORS is, why it exists, and how it works, with clear examples you can relate to.
What Is CORS?
Cross-Origin Resource Sharing (CORS) is a security mechanism based on HTTP headers that allows a server to specify which origins (domain, scheme, or port) are permitted to access its resources from a browser.
By default, browsers enforce the Same-Origin Policy, which prevents JavaScript running on one origin from accessing resources on another origin. CORS provides a controlled way to relax that restriction.
Example of a Cross-Origin Request
Frontend: https://domain-a.com
Backend API: https://domain-b.com/data.json
If JavaScript from domain-a.com tries to fetch data from domain-b.com, the browser will only allow it if the server responds with the correct CORS headers.
Why Browsers Enforce CORS
CORS exists to protect users from malicious websites stealing sensitive data such as:
Cookies.
Authentication tokens.
Private user information.
APIs like fetch() and XMLHttpRequest automatically follow CORS rules to reduce these risks.
What Requests Use CORS?
CORS applies to many browser features, including:
fetch() and XMLHttpRequest
Web fonts loaded via @font-face
WebGL textures
Images or videos drawn to a canvas
CSS shapes that use external images
How CORS Works
The browser sends a request with an Origin header.
The server responds with CORS headers (or none).
The browser decides whether to allow JavaScript access to the response.
For some requests, the browser sends a preflight request first to ask the server for permission.
Simple Requests (No Preflight)
Some requests are considered simple and do not require a preflight.
Conditions for a Simple Request
Method is GET, HEAD, or POST
Only safe headers are used (e.g. Accept, Content-Type)
-
Content-Type is one of:
- application/x-www-form-urlencoded
- multipart/form-data
- text/plain
Example
fetch("https://bar.other/data.json")
.then(res => res.json())
.then(data => console.log(data));
Request Sent by Browser
Origin: https://foo.example
Server Response
Access-Control-Allow-Origin: *
The browser allows the response to be read by JavaScript.
If the server wants to restrict access:
Access-Control-Allow-Origin: https://foo.example
Preflighted Requests (OPTIONS)
Requests that can modify server data or use custom headers require a preflight.
What Triggers a Preflight?
HTTP methods like PUT, DELETE, or PATCH
Custom headers (e.g. Authorization, X-Custom-Header)
Non-simple Content-Type (e.g. application/json, text/xml)
Example Request
fetch("https://bar.other/doc", {
method: "POST",
headers: {
"Content-Type": "text/xml",
"X-PINGOTHER": "pingpong",
},
body: "<person><name>Arun</name></person>",
});
Preflight Request (Sent Automatically)
OPTIONS /doc
Origin: https://foo.example
Access-Control-Request-Method: POST
Access-Control-Request-Headers: content-type, x-pingother
Server Response
Access-Control-Allow-Origin: https://foo.example
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
Access-Control-Max-Age: 86400
If approved, the browser sends the actual request.
Credentialed Requests (Cookies & Auth)
By default, browsers do not send cookies in cross-origin requests.
To include credentials:
fetch(url, {
credentials: "include"
});
Required Server Headers
Access-Control-Allow-Origin: https://foo.example
Access-Control-Allow-Credentials: true
Important rules:
Access-Control-Allow-Origin cannot be *
Credentials + wildcard = blocked by browser
Common CORS Headers
Response Headers
Access-Control-Allow-Origin : Who can access the resource
Access-Control-Allow-Methods : Allowed HTTP methods
Access-Control-Allow-Headers : Allowed custom headers
Access-Control-Allow-Credentials : Allow cookies/auth
Access-Control-Max-Age : Cache preflight result duration
Access-Control-Expose-Headers : Headers visible to JS
Request Headers (Browser-Set)
Origin
Access-Control-Request-Method
Access-Control-Request-Headers
Top comments (0)