# Access-Control-Allow-Methods Header

> Learn how Access-Control-Allow-Methods specifies which HTTP methods are permitted for cross-origin requests in CORS preflight responses.

Source: https://howhttpworks.com/headers/access-control-allow-methods
Last reviewed: 2026-10-05

> **TL;DR:** `Access-Control-Allow-Methods: PUT, DELETE` permits those methods after a successful CORS preflight. It does not make an API read-only or authorize a user. `GET`, `HEAD`, and `POST` are CORS-safelisted; omitting them does not deny them.

## What is Access-Control-Allow-Methods?

This is the server's answer to [Access-Control-Request-Method](https://howhttpworks.com/headers/access-control-request-method) in an OPTIONS preflight. A browser checks a non-safelisted method against the returned list before sending the actual request. The header is not the HTTP `Allow` header used to describe a resource's supported methods.

## How Access-Control-Allow-Methods Works

Illustrative header excerpts for a DELETE preflight:

```http
OPTIONS /api/posts/123 HTTP/1.1
Host: api.example.com
Origin: https://app.example.com
Access-Control-Request-Method: DELETE
Access-Control-Request-Headers: authorization
```

```http
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: DELETE
Access-Control-Allow-Headers: Authorization
```

The method and the Authorization header need permission. The actual DELETE response still needs `Access-Control-Allow-Origin`; methods permission alone is insufficient.

## Syntax

```http
Access-Control-Allow-Methods: GET, HEAD, POST, PUT, PATCH, DELETE
```

Method tokens are case-sensitive. Use `PATCH`, not `patch`. `*` has wildcard meaning only when credentials mode is not `include`; with `include`, it is a literal method name. [MDN describes both the wildcard and safelisted methods](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Access-Control-Allow-Methods#directives).

## Common Examples

For a collection accepting JSON POSTs, return `GET, POST`. For an individual record accepting replacement and deletion, return `GET, PUT, DELETE`. Listing GET and POST can document intent, but does not add enforcement for those safelisted methods.

These policies describe different endpoints, rather than a universal API-wide list:

| Endpoint | Intended operations | Methods advertised |
| --- | --- | --- |
| `/api/posts` | Read a collection; create a post | `GET, POST` |
| `/api/posts/123` | Read, replace, or delete one post | `GET, PUT, DELETE` |
| `/api/search` | Read-only search | `GET, HEAD` |

A JSON POST can preflight because of Content-Type, even though POST itself is safelisted. It still needs permission for that header.

## Real-World Scenarios

An API returning only `Access-Control-Allow-Methods: GET` can still receive a cross-origin form POST. A read-only endpoint must reject writes in its route handler. CORS controls browser access, while non-browser clients can call the route independently of these headers.

For a read-only Express route, enforce the method independently of CORS. This fragment belongs before any write handlers:

```javascript
app.use('/api/search', cors({
  origin: 'https://app.example.com',
  methods: ['GET', 'HEAD']
}))
app.use('/api/search', (req, res, next) => {
  if (req.method === 'GET' || req.method === 'HEAD') return next()
  res.set('Allow', 'GET, HEAD').status(405).end()
})
app.get('/api/search', (_req, res) => res.json({ results: [] }))
```

Application-level CORS middleware consumes valid preflight requests before the method guard. The guard rejects an actual POST, including one sent by curl. [RFC 9110 requires Allow in a 405 response](https://www.rfc-editor.org/rfc/rfc9110.html#section-15.5.6).

## Server Implementation

Configure an existing Express app with the `cors` package:

```javascript
const cors = require('cors')
app.use('/api/posts', cors({
  origin: 'https://app.example.com',
  methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'],
  allowedHeaders: ['Content-Type', 'Authorization']
}))
```

Place this before the API handlers. The [middleware handles OPTIONS preflights](https://expressjs.com/en/resources/middleware/cors/); you still need real handlers for the methods advertised.

A narrower route can have a different policy from the surrounding API. Register it before a broad middleware that would answer its OPTIONS request:

```javascript
const readPolicy = cors({
  origin: 'https://app.example.com',
  methods: ['GET', 'HEAD']
})
app.options('/api/audit', readPolicy)
app.get('/api/audit', readPolicy, (_req, res) => res.json({ events: [] }))
```

This is only the CORS and read-handler portion; authorization still belongs on the actual audit route. If an earlier `app.use(cors(...))` already handles OPTIONS, this per-route policy will never answer that preflight.

For FastAPI, configure the list explicitly on the application serving these routes:

```python
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

app = FastAPI()
app.add_middleware(
    CORSMiddleware,
    allow_origins=["https://app.example.com"],
    allow_methods=["GET", "PUT", "DELETE"],
    allow_headers=["Content-Type", "Authorization"],
)
```

[FastAPI's middleware options](https://fastapi.tiangolo.com/tutorial/cors/) do not create PUT or DELETE endpoints. Keep the advertised list aligned with the handlers in that application.

## Getting Access-Control-Allow-Methods right

Handle OPTIONS, but do not add OPTIONS to the list merely because the preflight uses it. The list describes the *subsequent* request. OPTIONS belongs there only if you intend to permit an actual cross-origin OPTIONS request.

A preflight must return a successful status such as 204, not an authentication redirect or a 401. Authenticate and authorize the actual operation independently.

## HTTP Methods Overview

CORS-safelisted is not synonymous with safe: POST is safelisted but is not a safe method. GET and HEAD are safe; PUT and DELETE are idempotent but can change state. Idempotence refers to the intended effect of repeating a request, not identical response bodies or statuses. [RFC 9110 defines these properties](https://www.rfc-editor.org/rfc/rfc9110.html#section-9.2).

| Method | Safe | Idempotent | CORS-safelisted |
| --- | --- | --- | --- |
| GET | Yes | Yes | Yes |
| HEAD | Yes | Yes | Yes |
| POST | No | No | Yes |
| PUT | No | Yes | No |
| DELETE | No | Yes | No |

A repeated DELETE may return 404 after the first request removed the record; that does not change DELETE's idempotent semantics. Do not infer retry safety from the CORS safelist.

## Common Method Combinations

Return a list for the specific endpoint rather than copying every method into every preflight response. Advertising DELETE on a route that has no DELETE handler makes preflight succeed only to leave the actual request failing, often with 405.

Separate public reads from authenticated writes when they have different consumers. A public catalogue can allow `GET, HEAD`, while a management application permits `PUT, DELETE` on record routes. Ensure the management handlers still check the user's role. An allowlisted frontend origin does not imply that every user of that frontend has write permission.

## Testing Access-Control-Allow-Methods

```bash
curl -i -X OPTIONS https://api.example.com/api/posts/123 \
  -H 'Origin: https://app.example.com' \
  -H 'Access-Control-Request-Method: DELETE' \
  -H 'Access-Control-Request-Headers: authorization'
```

Compare the method with the returned list in browser Network. Do not manually set `Access-Control-Request-Method` in fetch: it is a forbidden request header. CORS failures surface to JavaScript as network errors, not a reliable message identifying which header failed. Inspect the console and OPTIONS response instead.

After inspecting OPTIONS, test a deletion against a disposable record in the browser:

```javascript
const response = await fetch('https://api.example.com/api/posts/123', {
  method: 'DELETE',
  headers: { Authorization: 'Bearer REPLACE_WITH_TOKEN' }
})
if (!response.ok) throw new Error(`DELETE returned HTTP ${response.status}`)
```

A readable 405 means the browser sent the operation and the application rejected its method. A rejected fetch needs Network/Console inspection: a denied preflight, a missing CORS header on the DELETE response, and a connection failure can all look like network errors to application code.

## Related Headers

- [Access-Control-Allow-Origin](https://howhttpworks.com/headers/access-control-allow-origin)
- [Access-Control-Allow-Headers](https://howhttpworks.com/headers/access-control-allow-headers)
- [Access-Control-Request-Method](https://howhttpworks.com/headers/access-control-request-method)
