How HTTP Works

Method · safe · idempotent

> OPTIONS /users/42 HTTP/1.1>  Origin: https://app.example.com>  Access-Control-Request-Method: PATCH

HTTP OPTIONS Method

Learn how HTTP OPTIONS requests discover server capabilities, supported methods, and handle CORS preflight checks for cross-origin requests.

Reviewed 5 min readintermediate3 sourcesMarkdown
Safe
Yes
Idempotent
Yes
Cacheable
No
Request body
Allowed, rarely used
Response body
Optional
Spec
RFC 9110 §9.3.7
On this page

TL;DR: OPTIONS asks about communication options for a resource. Browsers also use it for CORS preflight. Allow advertises 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.

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

  1. MDN Web Docs: OPTIONSdeveloper.mozilla.org
  2. RFC 9110: OPTIONSrfc-editor.org
  3. Fetch Standard: CORS preflightfetch.spec.whatwg.org

Keep going

Browse /search