# 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.

Source: https://howhttpworks.com/glossary/preflight-request
Last reviewed: 2026-10-04

> **TL;DR:** A preflight is an `OPTIONS` request the browser sends by itself before a cross-origin request that could have side effects, to ask the server whether it allows that method and those headers.

A CORS preflight request is an automatic `OPTIONS` request that a browser sends to a cross-origin URL before the actual request, when that request is not a "simple" one. The server's answer tells the browser whether to send the real request at all. Your JavaScript never sees the preflight and cannot add headers to it.

## What triggers one

You get a preflight when a cross-origin request uses a method other than `GET`, `HEAD` or `POST`, or sets a header outside the CORS-safelisted set (`Accept`, `Accept-Language`, `Content-Language`, `Content-Type` limited to three values, and `Range` in simple form). The two triggers people hit most are:

- `Authorization` header on any request
- `Content-Type: application/json`, because only `application/x-www-form-urlencoded`, `multipart/form-data` and `text/plain` are safelisted

## The exchange

```http
OPTIONS /v1/orders HTTP/1.1
Host: api.example.com
Origin: https://app.example.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: authorization, content-type
```

```http
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: authorization, content-type
Access-Control-Max-Age: 600
Vary: Origin
```

If the answer does not cover the method and headers, Chrome logs `Response to preflight request doesn't pass access control check` and the real request is never sent.

## Non-obvious facts

- **The preflight must succeed with an ok status.** A 401, 403, 404 or 500 on the `OPTIONS` request fails CORS even if your `PUT` handler is fine. Auth middleware that runs first is the usual culprit, because the preflight carries no cookies or `Authorization` header.
- **`Access-Control-Max-Age` is capped by the browser.** The default is 5 seconds, so without it every cross-origin call becomes two round trips. Chromium caps the value at 2 hours (7200) and Firefox at 24 hours.
- **Add `Vary: Origin` when you echo the origin.** Otherwise a CDN may cache one origin's preflight and serve it to another.
- **Credentials need exact values.** With `Access-Control-Allow-Credentials: true`, the wildcard `*` is not accepted for origin, headers or methods.
- **Redirects on a preflight fail.** The preflight response must not be a redirect, so a trailing-slash 301 on the API path breaks it.

## Go deeper

- [CORS guide](https://howhttpworks.com/guides/cors)
- [OPTIONS method](https://howhttpworks.com/methods/options)
- [Access-Control-Max-Age](https://howhttpworks.com/headers/access-control-max-age)
- [Access-Control-Request-Method](https://howhttpworks.com/headers/access-control-request-method)
- [CORS error: no Access-Control-Allow-Origin header](https://howhttpworks.com/debug/cors-no-access-control-allow-origin)
