Simple vs Preflight Requests — Understanding the CORS Classification
In this tutorial, you will learn about Simple vs Preflight Requests. We cover key concepts, practical examples, and best practices to help you master this topic.
Cross-origin requests fall into two categories: simple requests that proceed without a preflight, and preflight requests requiring an OPTIONS handshake, based on method, headers, and content-type criteria.
What You'll Learn
- The three criteria for a simple request
- Which methods, headers, and content types are allowed
- Why the browser distinguishes between the two
Why It Matters
Choosing simple requests avoids an extra round-trip, speeding up your application. Understanding the distinction helps you design APIs that minimize preflight overhead.
flowchart LR
A["Cross-Origin Request"] --> B{"Method is GET, HEAD, or POST?"}
B -->|"No"| C["PREFLIGHT REQUIRED"]
B -->|"Yes"| D{"Only simple headers?"}
D -->|"No"| C
D -->|"Yes"| E{"Content-Type is..."}
E -->|"text/plain"| F["SIMPLE"]
E -->|"application/x-www-form-urlencoded"| F
E -->|"multipart/form-data"| F
E -->|"application/json or others"| C
style C fill:#fecaca,stroke:#dc2626
style F fill:#86efac,stroke:#16a34a
Code Examples
// Simple request: GET with no custom headers
fetch('https://api.example.com/users')
// No preflight needed
// Preflight request: POST with JSON
fetch('https://api.example.com/users', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({name: 'Alice'})
})
// Preflight needed because content-type is application/json
// Preflight request: DELETE method
fetch('https://api.example.com/users/1', {
method: 'DELETE'
})
// Preflight needed because method is not GET/HEAD/POST
# Check if a request would be simple
SIMPLE_METHODS = {'GET', 'HEAD', 'POST'}
SIMPLE_CONTENT_TYPES = [
'text/plain',
'application/x-www-form-urlencoded',
'multipart/form-data'
]
SIMPLE_HEADERS = {
'accept', 'accept-language', 'content-language',
'content-type'
}
def is_simple_request(method, headers):
if method.upper() not in SIMPLE_METHODS:
return False
content_type = headers.get('Content-Type', '').lower()
if content_type and content_type not in SIMPLE_CONTENT_TYPES:
return False
for header in headers:
if header.lower() not in SIMPLE_HEADERS:
return False
return True
# Test if a request triggers preflight
# This DELETE request will always trigger preflight
curl -X DELETE https://api.example.com/resource/1 \
-H "Origin: https://app.example.com" \
-v 2>&1 | head -20
# This GET request is simple
curl -X GET https://api.example.com/resource \
-H "Origin: https://app.example.com" \
-v 2>&1 | head -20
Common Mistakes
1. Thinking All POST Requests Trigger Preflight
POST with content-type text/plain is simple and skips preflight.
2. Adding Custom Headers Accidentally
A single custom header like X-Requested-With triggers preflight.
3. Using PUT Instead of POST
PUT always triggers preflight. Use POST where possible to avoid it.
4. Assuming Same-Origin Requests Follow the Same Rules
Same-origin requests bypass all CORS checks. Preflight logic only applies cross-origin.
5. Forgetting That Fetch Defaults Change Behavior
fetch() defaults to same-origin mode but accepts cors mode. Using no-cors mode makes the response opaque.
Practice Questions
- What three HTTP methods qualify for simple requests?
- What content types are allowed in simple requests?
- Why do custom headers trigger preflight?
- Does adding a Content-Type header of application/json make a POST request preflighted?
- Can a browser make a simple request with credentials?
Answers:
- GET, HEAD, POST.
- text/plain, application/x-www-form-urlencoded, multipart/form-data.
- Custom headers may affect server behavior, so the server must explicitly allow them.
- Yes. application/json is not in the allowed list for simple requests.
- Yes, but with-with credentials, the server must respond with Access-Control-Allow-Credentials: true.
Challenge: For a given API endpoint, list all possible request combinations and classify each as simple or preflighted. Include variations of method, content-type, and custom headers.
FAQ
Mini Project
Create a test page with buttons for each request type: simple GET, POST with form data, POST with JSON, PUT, DELETE, and GET with custom header. Display whether each request triggered a preflight by inspecting the Network tab. Log the request type and the CORS headers received.
What's Next
Study the complete CORS headers reference to understand every Access-Control-* header, then deep dive into Access-Control-Allow-Origin behavior.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro