Access-Control-Allow-Methods (tested)
Access-Control-Allow-Methods goes on the response to a CORS preflight and lists the methods the browser may use for the real request. It only matters for methods that trigger a preflight. We tried seven header values against six methods and logged what reached the server.
1. The results
Page at http://localhost:8973, cross-origin fetch() to http://127.0.0.1:8974. Every response carried Access-Control-Allow-Origin: * (or the exact origin plus Access-Control-Allow-Credentials: true in the credentials row). POST used Content-Type: text/plain, so it stayed a simple request. "OK" means the request reached the server and fetch resolved; "blocked" means only the OPTIONS preflight reached the server and fetch threw TypeError: Failed to fetch.
| Access-Control-Allow-Methods on preflight | GET | POST | PUT | DELETE | PATCH | patch |
|---|---|---|---|---|---|---|
| (absent) | OK, no preflight | OK, no preflight | blocked | blocked | blocked | blocked |
GET, POST | OK, no preflight | OK, no preflight | blocked | blocked | blocked | blocked |
PUT | OK, no preflight | OK, no preflight | OK | blocked | blocked | blocked |
put (lowercase) | OK, no preflight | OK, no preflight | blocked | blocked | blocked | blocked |
* | OK, no preflight | OK, no preflight | OK | OK | OK | blocked (see 3) |
* + credentials: 'include', exact ACAO, ACAC true | OK, no preflight | OK, no preflight | blocked | blocked | blocked | blocked |
PUT, DELETE | OK, no preflight | OK, no preflight | OK | OK | blocked | blocked |
Every "OK" for PUT, DELETE and PATCH was two requests on the server: OPTIONS then the real method. Every "blocked" was the OPTIONS alone; the real request never left the browser. The console error was:
Access to fetch at 'http://127.0.0.1:8974/x3' from origin 'http://localhost:8973' has been blocked by CORS policy: Method PUT is not allowed by Access-Control-Allow-Methods in preflight response.
2. What this means
- GET and POST never needed it. They are CORS-safelisted methods, so no preflight was sent and the header was irrelevant. Listing
GET, POSTdoes nothing for PUT, DELETE or PATCH. - Only the listed methods pass.
PUT, DELETEallowed exactly those two and still blocked PATCH. - The match is case-sensitive.
fetch()uppercasesput, so a server answeringputdid not allow thePUTrequest. *is a wildcard only without credentials. Withcredentials: 'include', Chromium treated*as a literal method name and blocked PUT, DELETE and PATCH even though Access-Control-Allow-Origin and Access-Control-Allow-Credentials were correct. With credentials, list the methods explicitly.
3. The lowercase patch trap
fetch() uppercases only delete, get, head, options, post and put. Our server logged the preflight as Access-Control-Request-Method: patch, lowercase, and no header value we tried let it through, not even *. In the * row Chromium reported a different and misleading error, even though our preflight did carry Access-Control-Allow-Origin:
... has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
We did not test a server that answers literally patch. The simple fix is to write method: 'PATCH' in your code.
4. JSON POST needs a preflight, but not this header
A POST with Content-Type: application/json did send a preflight (OPTIONS with Access-Control-Request-Method: POST). Without Access-Control-Allow-Headers it was blocked with "Request header field content-type is not allowed by Access-Control-Allow-Headers in preflight response." With Access-Control-Allow-Headers: Content-Type it succeeded, both with no Access-Control-Allow-Methods at all and with Access-Control-Allow-Methods: GET. POST is safelisted, so the methods list did not need to mention it.
5. Answering OPTIONS in nginx (not executed)
This snippet is doc-based; we did not run it. Adjust the origin and the lists to your API.
location /api/ {
if ($request_method = OPTIONS) {
add_header Access-Control-Allow-Origin "https://app.example.com" always;
add_header Access-Control-Allow-Methods "GET, POST, PUT, PATCH, DELETE" always;
add_header Access-Control-Allow-Headers "Content-Type, Authorization" always;
add_header Access-Control-Max-Age 600 always;
add_header Vary Origin always;
return 204;
}
add_header Access-Control-Allow-Origin "https://app.example.com" always;
add_header Vary Origin always;
proxy_pass http://backend;
}
Two things to keep in mind: the actual response still needs Access-Control-Allow-Origin (the methods header only matters on the preflight), and some backends return 404 or 405 for OPTIONS, which fails the preflight before the methods list is even read.
Related: when the browser sends the Origin header, Access-Control-Max-Age (preflight caching), Access-Control-Allow-Credentials and allowing multiple origins in CORS.
Frequently asked questions
Do I need Access-Control-Allow-Methods for GET and POST?
No. In our Chromium 153 test, cross-origin GET and POST (with a simple content type) sent no preflight and succeeded with the header absent. Even a JSON POST, which is preflighted, succeeded without it once Access-Control-Allow-Headers allowed Content-Type.
Is Access-Control-Allow-Methods case-sensitive?
Yes. A preflight answered with 'put' did not allow a PUT request; Chromium blocked it with 'Method PUT is not allowed by Access-Control-Allow-Methods in preflight response.'
Does Access-Control-Allow-Methods: * work?
Only for requests without credentials. Without credentials, * allowed PUT, DELETE and PATCH. With credentials: 'include' (and a correct exact origin plus Allow-Credentials: true), PUT, DELETE and PATCH were all blocked.
Why does my PATCH request fail CORS when PUT works?
Either PATCH is missing from your Access-Control-Allow-Methods list, or your code uses lowercase method: 'patch'. fetch() does not uppercase patch, and in our test a lowercase patch request was blocked even with *.
Where does Access-Control-Allow-Methods go, preflight or actual response?
On the preflight (OPTIONS) response; that is where the browser checks it. The actual response still needs Access-Control-Allow-Origin.
Written with AI assistance. Measured on 2026-10-01 in full Chromium 153.0.8010.12 (Playwright, channel 'chromium', fresh browser context per row). The page was served from http://localhost:8973 and a Node.js API on http://127.0.0.1:8974 (a different origin) logged the method of every request, OPTIONS preflights included, and answered preflights with 204 plus the Access-Control-Allow-Methods value under test. The nginx snippet and the notes on other browsers are doc-based, not executed. PlainHeaders is our extension; the other methods are free.