1. Library
  2. Http and the Web
  3. Methods

Updated 10 hours ago

The OPTIONS method asks servers a simple question: "What can I do with this resource?" But that's the boring answer.

The interesting truth: OPTIONS exists because browsers can't trust the web pages they're running. When JavaScript on one website tries to talk to a different server, the browser steps in and asks that server: "Hey, some code wants to send you a request. Is that okay?"

This is the preflight request—and it's OPTIONS doing the asking.

What OPTIONS Actually Returns

OPTIONS requests information about what's allowed:

OPTIONS /api/articles/12345 HTTP/1.1
Host: api.example.com
→
HTTP/1.1 204 No Content
Allow: GET, HEAD, PUT, DELETE, OPTIONS
Access-Control-Allow-Methods: GET, PUT, DELETE
Access-Control-Allow-Headers: Authorization, Content-Type
Access-Control-Max-Age: 86400

The Allow header lists every method the server supports for this resource. The Access-Control-* headers are for CORS—they tell browsers what cross-origin requests are permitted.

The Preflight Dance

Here's where OPTIONS earns its keep.

When JavaScript on https://app.example.com wants to make this request:

fetch('https://api.example.com/articles/123', {
  method: 'PUT',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer token123'
  },
  body: JSON.stringify({ title: 'Updated Title' })
});

The browser doesn't just send it. It can't. This is a cross-origin request that could modify data. The browser is protecting the server from JavaScript it doesn't trust—including JavaScript the user chose to run.

So the browser automatically sends a preflight:

OPTIONS /articles/123 HTTP/1.1
Host: api.example.com
Origin: https://app.example.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: Content-Type, Authorization

Translation: "JavaScript from app.example.com wants to PUT with these headers. Allow it?"

The server responds:

HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 86400

"Yes, that origin can PUT with those headers. And you can remember this answer for 24 hours."

Only then does the browser send the actual PUT request.

Why Some Requests Skip Preflight

Not every cross-origin request triggers OPTIONS. "Simple requests" go through directly:

  • Method: GET, HEAD, or POST
  • Headers: Only basics like Accept, Accept-Language, Content-Type
  • Content-Type: Only application/x-www-form-urlencoded, multipart/form-data, or text/plain

These mirror what HTML forms could always do—before JavaScript, before fetch, before APIs. The browser treats them as safe because they've always been possible.

Anything else triggers preflight:

// No preflight (simple GET)
fetch('https://api.example.com/data');

// Preflight (DELETE isn't simple)
fetch('https://api.example.com/data', { method: 'DELETE' });

// Preflight (Authorization header isn't simple)
fetch('https://api.example.com/data', {
  headers: { 'Authorization': 'Bearer token' }
});

// Preflight (JSON content type isn't simple)
fetch('https://api.example.com/data', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ data: 'value' })
});

Caching Preflight Responses

Access-Control-Max-Age: 86400 tells browsers to cache the preflight response for 24 hours. Without this, every single PUT or DELETE would require two round trips—OPTIONS then the actual request.

Browsers cap this cache (usually a few hours to a few days), so don't expect week-long caching even if you ask for it.

Implementing OPTIONS

Most frameworks handle OPTIONS automatically:

// Express with CORS middleware
const cors = require('cors');

app.use(cors({
  origin: 'https://app.example.com',
  methods: ['GET', 'PUT', 'DELETE'],
  allowedHeaders: ['Content-Type', 'Authorization'],
  maxAge: 86400
}));

If you're implementing it manually:

app.options('/api/articles/:id', (req, res) => {
  res.set('Allow', 'GET, HEAD, PUT, DELETE, OPTIONS');
  res.set('Access-Control-Allow-Origin', req.get('Origin'));
  res.set('Access-Control-Allow-Methods', 'GET, PUT, DELETE');
  res.set('Access-Control-Allow-Headers', 'Content-Type, Authorization');
  res.set('Access-Control-Max-Age', '86400');
  res.status(204).end();
});

OPTIONS for Server-Wide Discovery

You can send OPTIONS to * to query the entire server:

OPTIONS * HTTP/1.1
Host: api.example.com
→
HTTP/1.1 200 OK
Allow: GET, POST, PUT, DELETE, HEAD, OPTIONS

This asks "what methods does this server support in general?" rather than "what can I do with this specific resource?"

Security: Don't Leak Information

OPTIONS should respect authentication. Don't reveal that secret resources exist:

OPTIONS /admin/secret-data HTTP/1.1
→ 401 Unauthorized (if not authenticated)
→ 403 Forbidden (if not authorized)

Never return Allow: GET, PUT, DELETE for resources the user shouldn't know about.

And this combination is dangerous:

// Don't do this
res.set('Access-Control-Allow-Origin', '*');
res.set('Access-Control-Allow-Credentials', 'true');

This allows any website to make authenticated requests to your API. Validate origins:

const allowedOrigins = [
  'https://app.example.com',
  'https://mobile.example.com'
];

const origin = req.get('Origin');
if (allowedOrigins.includes(origin)) {
  res.set('Access-Control-Allow-Origin', origin);
}

Debugging CORS with OPTIONS

When CORS fails mysteriously, OPTIONS is your diagnostic tool:

curl -X OPTIONS https://api.example.com/resource \
  -H "Origin: https://app.example.com" \
  -H "Access-Control-Request-Method: PUT" \
  -H "Access-Control-Request-Headers: Content-Type" \
  -i

This shows you exactly what the server allows—or why it's rejecting your preflight.

Key Takeaways

  • OPTIONS asks "what can I do with this resource?" and returns allowed methods in the Allow header.
  • Its real purpose is CORS preflight: the browser asking servers for permission before letting JavaScript make cross-origin requests.
  • Simple requests (basic GET, HEAD, POST with simple headers) skip preflight. Everything else triggers it.
  • Cache preflight responses with Access-Control-Max-Age to avoid doubling every API call.
  • Don't leak information—respect authentication in OPTIONS responses just like any other method.
  • The browser is the gatekeeper. OPTIONS is how servers tell the browser what to allow through.

Frequently Asked Questions About HTTP OPTIONS

Was this page helpful?

😔
🤨
😃
The HTTP OPTIONS Method • Library • Connected