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.

May 6, 2025 - 06:37
 0
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:

Code for fetch
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...

Screenshot showing the OPTIONS request followed by the PUT request

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.