Simple vs. Preflighted Requests in CORS: What Developers Need to Know
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.

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.