TL;DR:
- Simple requests: sent directly, response blocked if CORS headers are missing.
- Preflighted requests: OPTIONS first, then the real request if allowed.
- HTML forms are why simple requests are still allowed today.
- CORS protects responses, not the requests.
Let’s Send a PUT Request and Inspect the Network Tab
Say you’re making a simple fetch call in JavaScript like this:
You might expect the browser to just send that PUT request to your server.
But when you check the Network tab in your browser's developer tools...
Figure 1: Screenshot showing the OPTIONS request followed by the PUT request
...you'll notice the browser sends an OPTIONS request first then the PUT. What’s going on?
This is what's called a preflight request — a kind of sanity check that the browser does before making certain types of cross-origin requests.
So What Is a Preflight Request?
A preflight (or "preflighted") request is when the browser sends an HTTP OPTIONS request before the actual request (like PUT or DELETE) to ask the server:
“Hey, I'm about to send a non-standard request. Are you okay with this?”
Only if the server responds with the right CORS headers, will the browser then send the real request.
This is a security measure, because certain requests can change server state or carry sensitive data.
Quick Refresher: What Is CORS?
Without a deep dive, CORS (Cross-Origin Resource Sharing) is:
A browser security feature that controls how web pages can make requests to domains other than the one that served them.
What’s a Simple Request?
Not all requests go through this preflight step. Some are "simple" — a term from the older CORS spec (though the newer Fetch spec no longer uses it explicitly).
A simple request is one that satisfies all of the following:
✅ Allowed Methods: GET, HEAD, POST
✅ Allowed Headers:
Only CORS-safelisted headers like:
Accept, Accept-Language, Content-Language
Content-Type — but only with:
- application/x-www-form-urlencoded
- multipart/form-data
- text/plain
✅ Other Rules:
- No custom headers like Authorization or X-Whatever
- No ReadableStream in the body
- No xhr.upload.addEventListener(...) handlers
These constraints are what keep simple requests “safe enough” that the browser doesn’t feel the need to pre-check them with the server.
So Now We Have Two Types of Requests
Let’s break down how simple requests and preflighted requests interact differently with the server and browser:
❌ If a CORS Error Happens:
Simple request:
✅ The request is sent to the server. Process and send the response back with headers added.
🚫 But if the server doesn't return the right Access-Control-Allow-Origin, the browser blocks the response.
Preflighted request:
🚫 The browser sends a preflight OPTIONS request.
❌ If the server doesn't respond correctly, the actual request is never sent.
Figure 2: Shows the flow when a browser requests a simple request.
Figure 3: Shows the flow when a browser requests a preflight request.
Why Are Simple Requests Even Allowed Now?
It goes back to the HTML<form>
tag from way back in HTML 4.0
.
Before JavaScript could make HTTP requests using fetch() or XMLHttpRequest
, you could already do this:
<form method="POST" action="https://otherdomain.com/submit">
...
</form>
This would submit data across origins, no problem. So servers had to defend against that kind of cross-site behavior — namely, CSRF (Cross-Site Request Forgery).
Because simple requests look like form submissions, they’re no more dangerous than what browsers could already do decades ago. That’s why:
CORS doesn’t block simple requests. They’re not introducing a new threat.
But—and this is key—the response is still blocked unless the server opts in by including the Access-Control-Allow-Origin header.
So, What Protection Still Exists?
Even when the browser allows the request to go out:
The response is blocked unless the server returns:
Access-Control-Allow-Origin: http://your-frontend-domain.com
So yes — the request can happen, just like a form could do it...
But JavaScript can’t access the response unless the server explicitly says it can.
Why Do We Need Preflight at All?
Preflight requests were introduced so browsers could:
✅ Verify the server is CORS-aware
✅ Get explicit permission for potentially dangerous or unusual requests
These are typically:
- Methods other than GET, POST, HEAD (like PUT, DELETE)
- Requests with custom headers (e.g. Authorization)
- Requests with bodies like application/json
Here’s what the original CORS spec said:
“To protect resources against cross-origin requests that could not originate from certain user agents before this specification existed, a preflight request is made to ensure that the resource is aware of this specification.”
Translation:
Only let “new” types of cross-origin requests through if the server knows about CORS and says “yes”.
🧠 Let’s wrap it up:
- Simple requests are like old-school form submissions. They're allowed without preflight — but the browser blocks the response unless the server opts in.
- Preflighted requests are more powerful or unusual. The browser checks with the server first using OPTIONS. If the server doesn't opt in, the actual request never goes out.
- The protection lies in the server's response: unless it sends the right CORS headers, JavaScript can’t read the response.
Top comments (0)