Method · safe · idempotent
> OPTIONS /users/42 HTTP/1.1> Origin: https://app.example.com> Access-Control-Request-Method: PATCHHTTP OPTIONS Method
Learn how HTTP OPTIONS requests discover server capabilities, supported methods, and handle CORS preflight checks for cross-origin requests.
- Safe
- Yes
- Idempotent
- Yes
- Cacheable
- No
- Request body
- Allowed, rarely used
- Response body
- Optional
- Spec
- RFC 9110 §9.3.7
On this page
- What is OPTIONS?
- Key Characteristics
- How OPTIONS Works
- Real-World Examples
- When to Use OPTIONS
- CORS Preflight Explained
- Common Response Headers
- Response Patterns
- Common Response Codes
- Getting OPTIONS right
- CORS Configuration Examples
- nginx in front of one API
- Debugging CORS Issues
- Try It Yourself
- Related Methods
- Related Concepts
TL;DR: OPTIONS asks about communication options for a resource. Browsers also use it for CORS preflight.
Allowadvertises HTTP methods;Access-Control-Allow-*grants browser CORS permission. They are separate mechanisms.
What is OPTIONS?
OPTIONS /users/42 targets one resource. OPTIONS * targets the server in general, rather than every route. RFC 9110 §9.3.7 does not define a universal discovery response body.
To address the server-wide asterisk form with curl:
curl -i -X OPTIONS --request-target '*' 'https://api.example.com/'
Compare it with an OPTIONS request to /users/42. A server may return different capability information for those targets. Neither response is a catalog of every application route.
Key Characteristics
OPTIONS is safe and idempotent: it requests information rather than a business-state change. Its response can change when capabilities change. OPTIONS responses are not HTTP-cacheable. Browser preflight permissions are kept in a separate CORS preflight cache.
How OPTIONS Works
curl -i -X OPTIONS 'https://api.example.com/users/42'
Illustrative capability response:
HTTP/1.1 204 No Content
Date: Mon, 05 Oct 2026 12:00:00 GMT
Allow: GET, HEAD, PUT, DELETE, OPTIONS
A successful OPTIONS response should advertise applicable capabilities, such as Allow, but Allow is not mandatory on every OPTIONS response. It is mandatory on a 405 response.
Real-World Examples
Reproduce the shape of a browser preflight with curl:
curl -i -X OPTIONS 'https://api.example.com/users/42' \
-H 'Origin: https://app.example.com' \
-H 'Access-Control-Request-Method: PUT' \
-H 'Access-Control-Request-Headers: content-type, authorization'
Curl shows what the server sends; it does not enforce browser CORS rules.
When to Use OPTIONS
Use it to inspect advertised capabilities or investigate a failed preflight. Do not infer user authorization from an allowed-method list: the actual PUT or DELETE still needs access checks.
CORS Preflight Explained
A cross-origin Fetch request using PUT, PATCH, DELETE, JSON content, or Authorization generally needs preflight unless a matching preflight permission is already cached. Same-origin requests do not need CORS preflight merely because they use these methods.
The preflight carries the intended method and header names, not the Bearer token or cookies. The Fetch Standard requires a successful status for preflight. Authentication middleware that returns 401 before handling OPTIONS prevents the actual request.
This cross-origin browser request needs permission for both its method and JSON request header:
const response = await fetch('https://api.example.com/users/42', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ displayName: 'Avery' })
})
if (!response.ok) throw new Error(`PUT failed: ${response.status}`)
In the Network panel, the preflight should advertise PUT in Access-Control-Request-Method and content-type in Access-Control-Request-Headers. These are requests for permission, not response grants. Adding Access-Control-Allow-Origin to this JavaScript request cannot grant itself access.
Common Response Headers
Access-Control-Allow-Origin grants access to one origin or, for requests whose credentials mode is not include, *. Access-Control-Allow-Methods lists permitted methods; Access-Control-Allow-Headers lists permitted request headers. Authorization must be named explicitly; the header wildcard does not cover it.
Response Patterns
Illustrative preflight response for a cookie-authenticated client:
HTTP/1.1 204 No Content
Date: Mon, 05 Oct 2026 12:00:00 GMT
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods: PUT
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 600
Vary: Origin
Here 600 seconds is a chosen permission lifetime, not a browser default or guarantee. Browsers cap it. The actual response must also carry the required origin and credentials headers.
Common Response Codes
200 can include a capability document; 204 has no content. A 405 needs Allow. For browser preflight, a non-success status such as 401 or 403 fails the check regardless of an otherwise correct allow-origin header.
Getting OPTIONS right
Keep preflight ahead of application authentication, then authenticate and authorize the actual operation. Match the requested origin against an allowlist. If the response changes by origin, include Vary: Origin so an HTTP cache does not reuse the wrong variant.
CORS Configuration Examples
This Express route handles preflight for one endpoint. The GET response also grants access:
const express = require('express')
const app = express()
app.use('/catalog', (_req, res, next) => {
res.set('Access-Control-Allow-Origin', 'https://app.example.com')
next()
})
app.options('/catalog', (_req, res) => {
res.set('Access-Control-Allow-Headers', 'Authorization')
res.status(204).end()
})
app.get('/catalog', (_req, res) => res.json({ items: [] }))
app.listen(3000)
This fixed-origin example does not use cookies. A GET preflight caused by Authorization needs the allowed header; GET itself is a CORS-safelisted method.
For a larger Express application, the maintained cors middleware handles preflight when mounted at application level:
const cors = require('cors')
app.use(cors({
origin: ['https://app.example.com', 'https://admin.example.com'],
methods: ['GET', 'HEAD', 'PUT'],
allowedHeaders: ['Content-Type', 'Authorization'],
credentials: true,
maxAge: 600
}))
Mount it before the actual routes and their authentication middleware. This is an alternative to the manual handler above. The middleware documentation specifies the option names and application-level OPTIONS handling. The allowed-method list should describe methods your endpoint implements; CORS middleware does not add a PUT implementation.
nginx in front of one API
This fragment belongs inside an nginx server block. It grants one fixed frontend origin, handles OPTIONS locally, and proxies actual requests:
location /api/ {
add_header Access-Control-Allow-Origin "https://app.example.com" always;
add_header Access-Control-Allow-Methods "GET, HEAD, PUT" always;
add_header Access-Control-Allow-Headers "Content-Type, Authorization" always;
add_header Access-Control-Max-Age "600" always;
if ($request_method = OPTIONS) {
return 204;
}
proxy_pass http://127.0.0.1:3000;
}
The add_header always parameter matters for error responses. Keep one layer responsible for CORS headers; an upstream that also adds allow-origin can produce duplicates. No credentials grant is configured here. If cookie credentials are needed, add the appropriate credentials policy to both exchanges.
Debugging CORS Issues
Inspect both OPTIONS and the actual response in DevTools. A valid preflight followed by a response without allow-origin still fails. An absent OPTIONS can mean no preflight was needed or a cached permission was reused.
When OPTIONS succeeds but the operation fails, distinguish three cases: no actual request was sent, an actual request returned an HTTP error, or the browser refused to expose its response. In DevTools, look at the method, status, and headers for each entry rather than only the console message.
A 405 with Allow: GET, HEAD means the endpoint does not allow OPTIONS. Returning that header alone does not make a browser preflight succeed. A 204 with a missing requested header grant can also fail despite its success status.
Try It Yourself
Use the CORS debugger to compare GET with and without Authorization.
Inspect Access-Control-Request-Headers: authorization in the simulated preflight. Removing Authorization from allow-headers should fail that permission check even if allow-origin is correct.
Related Methods
Related Concepts
In practice
Express.js
// CORS preflight — Express handles OPTIONS via cors() middleware
const cors = require('cors')
app.use(cors({
origin: ['https://app.example.com'],
methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization'],
maxAge: 86400 // Cache preflight for 24 hours
}))
// Manual OPTIONS handler (if not using cors middleware)
app.options('/api/*', (req, res) => {
res.set({
'Access-Control-Allow-Origin': 'https://app.example.com',
'Access-Control-Allow-Methods': 'GET, POST, PUT, PATCH, DELETE',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
'Access-Control-Max-Age': '86400'
}).status(204).end()
})Next.js App Router
// app/api/users/route.ts
// Handle CORS preflight explicitly
export async function OPTIONS() {
return new Response(null, {
status: 204,
headers: {
'Access-Control-Allow-Origin': 'https://app.example.com',
'Access-Control-Allow-Methods': 'GET, POST, PUT, PATCH, DELETE',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
'Access-Control-Max-Age': '86400'
}
})
}
// Or configure globally in next.config.ts headers()Go net/http
func corsMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "https://app.example.com")
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
if r.Method == http.MethodOptions {
w.Header().Set("Access-Control-Max-Age", "86400")
w.WriteHeader(http.StatusNoContent)
return
}
next.ServeHTTP(w, r)
})
}Frequently asked questions
What is the HTTP OPTIONS method?
OPTIONS requests communication options for the target resource, or the server in general with an asterisk target. A successful response can advertise methods with Allow; browsers also use OPTIONS for CORS preflight.
What is a CORS preflight request?
A browser preflight is an OPTIONS request sent before certain cross-origin requests unless matching permission is cached. It asks permission for the intended method and non-safelisted request headers.
What headers does OPTIONS return?
A successful OPTIONS response can advertise capabilities such as Allow or Accept-Patch. CORS preflight uses Access-Control-Allow-Origin and applicable allow-methods and allow-headers fields; Allow alone does not grant CORS permission.
Is OPTIONS safe and idempotent?
OPTIONS is safe and idempotent: it requests information, not a business-state change. Its response can change as capabilities change, and logging each request is allowed.
Sources
Related
HTTP TRACE Method: What It Does and Why to Disable It
HTTP TRACE echoes the request back for diagnostics. Cross-Site Tracing history, why scanners flag "TRACE enabled", and how to disable it in Apache and nginx.
Preflight Request (CORS OPTIONS)
A CORS preflight is an automatic OPTIONS request the browser sends before a cross-origin request to check permission. See when it fires and how to answer it.
HTTP HEAD Method
Learn how HTTP HEAD requests retrieve resource metadata (headers) without downloading the body. Useful for checking existence, size, and modification dates.
HTTP QUERY Method: Safe Requests With a Body (RFC 10008)
HTTP QUERY is a safe, idempotent method with the query in the body. RFC 10008 status, an example exchange, caching rules, and why GET with a body fails.