# HTTP OPTIONS Method

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

Source: https://howhttpworks.com/methods/options
Last reviewed: 2026-10-05

> **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](https://www.rfc-editor.org/rfc/rfc9110.html#section-9.3.7) does not define a universal discovery response body.

To address the server-wide asterisk form with curl:

```bash
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

```bash
curl -i -X OPTIONS 'https://api.example.com/users/42'
```

Illustrative capability response:

```http
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:

```bash
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](https://fetch.spec.whatwg.org/#cors-preflight-fetch) 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:

```javascript
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
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:

```javascript
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:

```javascript
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](https://expressjs.com/en/resources/middleware/cors/) 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:

```nginx
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](https://nginx.org/en/docs/http/ngx_http_headers_module.html#add_header) 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](https://howhttpworks.com/tools/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

- [GET](https://howhttpworks.com/methods/get)
- [PATCH](https://howhttpworks.com/methods/patch)

## Related Concepts

- [CORS guide](https://howhttpworks.com/guides/cors)
- [Preflight request](https://howhttpworks.com/glossary/preflight-request)
