HTTP OPTIONS Method: CORS, Preflight & API Discovery
Learn how the OPTIONS method works, why browsers use it for CORS preflight, and how to implement it correctly in your REST API.
What is the HTTP OPTIONS Method?
The HTTP OPTIONS method describes the communication options for a target resource. It asks the server: "What can I do with this resource, and how?" The server responds with headers that list the allowed methods, accepted headers, and CORS permissions — but takes no action on the resource itself.
OPTIONS is defined in RFC 9110 (HTTP Semantics) as a safe and idempotent method — it does not modify server state, and calling it multiple times always produces the same response.
Two Primary Use Cases
- CORS Preflight: Browsers automatically send OPTIONS before certain cross-origin requests to check whether the server permits the request. This is by far the most common use of OPTIONS in modern web APIs.
- API Discovery / Introspection: Clients can send OPTIONS to discover what HTTP methods a resource supports, using the
Allowresponse header.
In practice, most developers encounter OPTIONS exclusively through CORS preflight errors. Understanding exactly what OPTIONS does — and what your server must return — is the key to resolving those errors permanently.
CORS Preflight Requests Explained
What is CORS?
CORS (Cross-Origin Resource Sharing) is a browser security mechanism that restricts web pages from making HTTP requests to a different origin (domain, protocol, or port) than the one that served the page. Without CORS, a malicious website could silently make authenticated requests to your API using the user's credentials.
When a web application at https://app.example.com wants to call an API at https://api.example.com, the browser treats this as a cross-origin request and enforces CORS rules.
When Does the Browser Send a Preflight?
Not every cross-origin request triggers a preflight. The browser only sends an OPTIONS preflight for non-simple requests. A request is non-simple if it meets any of these conditions:
- Uses a method other than GET, HEAD, or POST
- Uses POST with a
Content-Typeother thanapplication/x-www-form-urlencoded,multipart/form-data, ortext/plain - Includes custom request headers (e.g.,
Authorization,X-Custom-Header) - Uses credentials (cookies, HTTP authentication)
In practice, virtually every REST API call from a frontend application triggers a preflight, because they use Content-Type: application/json and/or an Authorization header.
Step-by-Step Preflight Flow
STEP 1: Browser wants to make this actual request
POST https://api.example.com/api/v1/users
Origin: https://app.example.com
Content-Type: application/json
Authorization: Bearer token123
STEP 2: Before sending it, browser sends preflight
OPTIONS https://api.example.com/api/v1/users
Origin: https://app.example.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Content-Type, Authorization
STEP 3: Server responds with CORS permissions
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 86400
STEP 4: Browser checks: is POST allowed? Yes.
Are Content-Type and Authorization allowed? Yes.
Origin is approved? Yes.
STEP 5: Browser proceeds with the actual POST request.
Full Preflight Request and Response Example
Preflight Request (sent by browser automatically)
OPTIONS /api/v1/users HTTP/1.1
Host: api.example.com
Origin: https://app.example.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Content-Type, Authorization
Connection: keep-alive
Preflight Response (your server must return this)
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETE, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 86400
Vary: Origin
Content-Length: 0
OPTIONS Response Headers Reference
The following headers control what cross-origin requests are allowed. Understanding each one is essential for correctly implementing CORS in your API.
| Header | Direction | Description |
|---|---|---|
Origin |
Request | The origin of the requesting page. Set by the browser automatically — never by JavaScript. |
Access-Control-Request-Method |
Preflight Request | The HTTP method that will be used in the actual request. Sent in the preflight only. |
Access-Control-Request-Headers |
Preflight Request | Comma-separated list of request headers that will be sent in the actual request. |
Access-Control-Allow-Origin |
Response | The origin(s) permitted to access the resource. Use * for public APIs, or echo the specific Origin value for credentialed requests. |
Access-Control-Allow-Methods |
Preflight Response | Comma-separated list of HTTP methods the resource allows for cross-origin requests. Example: GET, POST, PUT, DELETE, OPTIONS. |
Access-Control-Allow-Headers |
Preflight Response | Comma-separated list of headers that may be used in the actual request. Must include any custom headers the client wants to send. |
Access-Control-Max-Age |
Preflight Response | How long (in seconds) the browser can cache this preflight response. Reduces preflight overhead. Maximum is browser-dependent (Chrome: 7200s, Firefox: 86400s). |
Access-Control-Allow-Credentials |
Response | Set to true to allow the browser to expose the response when the request includes credentials (cookies, HTTP auth). Cannot be combined with Access-Control-Allow-Origin: *. |
Access-Control-Expose-Headers |
Response | Lists response headers that are safe to expose to JavaScript. By default, only CORS-safelisted headers (Cache-Control, Content-Type, etc.) are accessible. |
Allow |
Response | Lists all HTTP methods supported by the resource. Used for API discovery, not CORS specifically. |
Vary: Origin |
Response | Tells CDNs and proxies that the response varies based on the Origin header. Essential when returning different Access-Control-Allow-Origin values per request. |
Implementing OPTIONS in Your API
Wildcard Origin vs Specific Origins
The Access-Control-Allow-Origin header has two modes:
- Wildcard (
*): Allows any origin. Suitable for truly public APIs with no authentication. Simple to implement but cannot be used withAccess-Control-Allow-Credentials: true. - Specific origin: Echo the exact value of the request's
Originheader (after validating it against your allowlist). Required for credentialed requests. Must also includeVary: Originto prevent cache poisoning.
Public API (wildcard — no credentials)
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETE, OPTIONS
Access-Control-Allow-Headers: Content-Type
Access-Control-Max-Age: 86400
Private API (specific origin — with credentials)
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETE, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 3600
Vary: Origin
Using Access-Control-Max-Age for Performance
Without caching, the browser sends an OPTIONS preflight before every non-simple cross-origin request. On a page that makes 20 API calls, that means 40 HTTP requests instead of 20. The Access-Control-Max-Age header tells the browser to cache the preflight result for a specified number of seconds, eliminating redundant preflight requests.
- Value of
86400(24 hours) is a good default for stable APIs - Use lower values (e.g.,
300) during development or when CORS config changes frequently - Browser maximums: Chrome enforces 7200s, Firefox 86400s, Safari varies
Security: Never Use Wildcard with Credentials
The combination of Access-Control-Allow-Origin: * and Access-Control-Allow-Credentials: true is explicitly invalid per the CORS specification. Browsers will reject such responses. This restriction exists by design: if you allow credentials (cookies, auth tokens), you must be specific about which origins you trust. Always validate the request's Origin against an allowlist and echo the approved origin in the response.
Wrong — invalid combination that browsers reject:
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true <-- INVALID with wildcard
Correct — specific origin with credentials:
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Credentials: true
Vary: Origin
OPTIONS for API Discovery
Beyond CORS, OPTIONS serves a second purpose: API introspection. A client can send OPTIONS to any URL and receive the Allow header listing all HTTP methods the resource accepts.
API Discovery Request
Response:
HTTP/1.1 204 No Content
Allow: GET, PUT, PATCH, DELETE, OPTIONS
Content-Length: 0
Collection Resource Discovery
Response:
HTTP/1.1 204 No Content
Allow: GET, POST, OPTIONS
Content-Length: 0
The Allow header is also returned automatically by the server in a 405 Method Not Allowed response — telling clients which methods the resource does support. This makes it easy for API clients to recover from incorrect method choices without consulting documentation.
More advanced API introspection systems (like those built on OpenAPI or JSON:API) may return richer metadata in the OPTIONS response body, but the Allow header is the standard, universally-supported mechanism.
Common OPTIONS / CORS Mistakes
Mistake 1: Returning 404 or 405 for OPTIONS Requests
If your router does not explicitly handle OPTIONS, it may return a 404 Not Found or 405 Method Not Allowed response. The browser receives this before it can check CORS headers, causing the preflight to fail and the actual request to be blocked. Every route that accepts cross-origin requests must handle OPTIONS — either explicitly or via middleware that intercepts OPTIONS before routing.
Mistake 2: Wildcard Origin with Credentials
As covered above, Access-Control-Allow-Origin: * and Access-Control-Allow-Credentials: true cannot be combined. Browsers reject this combination silently (from the JavaScript perspective) — the fetch or XMLHttpRequest fails with a CORS error, and the network tab shows the response was received but blocked. Always use a specific origin when credentials are involved.
Mistake 3: Missing Vary: Origin Header
When your server returns different values of Access-Control-Allow-Origin depending on the request's Origin, you must also return Vary: Origin. Without it, a CDN or proxy might cache a response with one origin's CORS headers and serve it to a different origin — causing that second origin to see incorrect or absent CORS headers. This produces intermittent CORS failures that are very hard to debug.
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://app.example.com
Vary: Origin <-- Required whenever Access-Control-Allow-Origin varies per request
Mistake 4: Access-Control-Max-Age Too Low or Too High
- Too low (e.g., 0 or 1): The browser preflights every single request, doubling your API call count and adding latency. This is a hidden performance problem that only shows up under realistic traffic.
- Too high: If you change your CORS policy (add a new allowed header, change allowed origins), users' browsers may cache the old preflight response for hours. During incident response, this means users are stuck with the old policy until their cache expires.
- Recommendation: Use 86400 (24 hours) for stable production APIs, 300-3600 for APIs with changing CORS policies.
Mistake 5: Not Including Required Headers in Access-Control-Allow-Headers
If your client sends a custom header — like Authorization, X-Request-ID, or X-API-Version — that header must appear in Access-Control-Allow-Headers. If it is missing, the browser blocks the actual request even after a successful preflight. Check the Access-Control-Request-Headers value in the preflight request to see exactly what the client is asking to send.
Frequently Asked Questions
What is a CORS preflight request?
A CORS preflight request is an HTTP OPTIONS request that the browser automatically sends before certain cross-origin requests. It asks the server whether the actual request — with its method and headers — is allowed from the requesting origin. The server responds with CORS headers indicating what is permitted. If the server approves, the browser proceeds with the actual request. If not, the browser blocks the request and the JavaScript code receives a CORS error.
Does every API request trigger an OPTIONS preflight?
No. The browser only sends a preflight OPTIONS request for non-simple requests. Simple requests — GET or POST with only basic headers and a Content-Type of application/x-www-form-urlencoded, multipart/form-data, or text/plain — do not trigger a preflight. In practice, the vast majority of REST API calls from frontend applications do trigger preflights, because they use Content-Type: application/json and/or an Authorization header. The Access-Control-Max-Age header lets you cache the preflight result to avoid repeated OPTIONS calls.
What should my server return for an OPTIONS request?
For a CORS preflight, return: HTTP 204 No Content (or 200 OK), Access-Control-Allow-Origin with the allowed origin, Access-Control-Allow-Methods listing permitted methods, Access-Control-Allow-Headers listing permitted request headers, Access-Control-Max-Age to cache the preflight, and Vary: Origin if you echo specific origins. For API discovery (non-CORS OPTIONS), return the Allow header listing all methods supported by the resource.
Why am I getting CORS errors even though I handle OPTIONS?
The most common causes are: (1) You return Access-Control-Allow-Origin: * but also set Access-Control-Allow-Credentials: true — this combination is invalid. (2) The Origin in the request does not match the value in Access-Control-Allow-Origin. (3) The request uses a header or method not listed in your CORS response headers. (4) You are missing Vary: Origin, causing a CDN to serve a cached response with the wrong origin. (5) Your OPTIONS handler returns the wrong status code, or CORS middleware runs after routing and never reaches OPTIONS requests that 404 first.
Related Topics
HTTP Methods Overview
Compare all HTTP methods: GET, POST, PUT, PATCH, DELETE, and OPTIONS.
HTTP PATCH Method
Partial updates, upsert patterns, idempotency, and JSON Patch format.
Authentication
API keys, JWT, and OAuth 2.0 — and how CORS interacts with auth.
HTTP Status Codes
204, 405, 412, and other codes relevant to OPTIONS handling.