- Home
- HTTP Headers
- Access-Control-Allow-Methods Header
Header
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.
TL;DR: Lists which HTTP methods are allowed for cross-origin requests. Methods like PUT, DELETE, PATCH must be explicitly allowed in CORS preflight responses.
What is Access-Control-Allow-Methods?
The Access-Control-Allow-Methods header is used in CORS preflight responses to tell the browser which HTTP methods are allowed when making cross-origin requests. It’s like a server saying “You can GET, POST, and DELETE from here, but not PATCH or PUT.”
This header is essential for controlling what operations clients can perform on your API from different origins.
How Access-Control-Allow-Methods Works
Browser sends preflight request:
OPTIONS /api/posts/123 HTTP/1.1
Host: api.example.com
Origin: https://app.example.com
Access-Control-Request-Method: DELETE
```http
**Server responds with allowed methods:**
```http
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, POST, DELETE
Access-Control-Max-Age: 86400
Browser then sends actual request:
DELETE /api/posts/123 HTTP/1.1
Host: api.example.com
Origin: https://app.example.com
Authorization: Bearer token123
```http
## Syntax
```http
Access-Control-Allow-Methods: <method>
Access-Control-Allow-Methods: <method>, <method>, ...
Access-Control-Allow-Methods: *
Values
- method - HTTP method name (GET, POST, PUT, DELETE, PATCH, OPTIONS, HEAD)
- * - Wildcard allowing all methods (use with caution)
Common Examples
Read-Only API
Access-Control-Allow-Methods: GET, HEAD, OPTIONS
```text
Only allows reading data, no modifications.
### Full CRUD API
```http
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, PATCH, OPTIONS
Allows all standard REST operations.
Public Read, Authenticated Write
# For public endpoints
Access-Control-Allow-Methods: GET, OPTIONS
# For authenticated endpoints
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
```text
### Wildcard (Use Carefully)
```http
Access-Control-Allow-Methods: *
Allows all HTTP methods (security risk if not properly controlled).
Real-World Scenarios
Blog API
# Preflight for creating a post
OPTIONS /api/posts HTTP/1.1
Host: api.blog.com
Origin: https://blog.com
Access-Control-Request-Method: POST
# Server response
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://blog.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 3600
```text
### Read-Only Public API
```http
# Preflight for data request
OPTIONS /api/products HTTP/1.1
Origin: https://shop.example.com
Access-Control-Request-Method: GET
# Server response (read-only)
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, HEAD, OPTIONS
Access-Control-Max-Age: 86400
Admin Dashboard API
# Preflight for user deletion
OPTIONS /api/admin/users/456 HTTP/1.1
Origin: https://admin.example.com
Access-Control-Request-Method: DELETE
# Server response
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://admin.example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, PATCH
Access-Control-Allow-Headers: Authorization
Access-Control-Max-Age: 7200
```javascript
## Server Implementation
### Express.js (Node.js)
```javascript
const express = require('express')
const cors = require('cors')
const app = express()
// Using cors middleware
app.use(
cors({
origin: 'https://app.example.com',
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization']
})
)
// Manual implementation
app.use((req, res, next) => {
res.setHeader('Access-Control-Allow-Origin', 'https://app.example.com')
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS')
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization')
if (req.method === 'OPTIONS') {
res.setHeader('Access-Control-Max-Age', '86400')
return res.status(204).end()
}
next()
})
// Different methods for different endpoints
app.options('/api/public/*', (req, res) => {
res.setHeader('Access-Control-Allow-Methods', 'GET, HEAD, OPTIONS')
res.status(204).end()
})
app.options('/api/admin/*', (req, res) => {
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, PATCH, OPTIONS')
res.status(204).end()
})
Method-Specific CORS
const express = require('express')
const app = express()
// Helper to set CORS headers
const setCORSHeaders = (res, methods) => {
res.setHeader('Access-Control-Allow-Origin', 'https://app.example.com')
res.setHeader('Access-Control-Allow-Methods', methods)
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization')
}
// Read-only endpoints
app.use('/api/public', (req, res, next) => {
setCORSHeaders(res, 'GET, HEAD, OPTIONS')
if (req.method === 'OPTIONS') return res.status(204).end()
next()
})
// Full CRUD endpoints
app.use('/api/admin', (req, res, next) => {
setCORSHeaders(res, 'GET, POST, PUT, DELETE, PATCH, OPTIONS')
if (req.method === 'OPTIONS') return res.status(204).end()
next()
})
```javascript
### FastAPI (Python)
```python
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI()
# Public API - read only
public_app = FastAPI()
public_app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_methods=["GET", "HEAD", "OPTIONS"],
allow_headers=["*"],
)
# Admin API - full CRUD
admin_app = FastAPI()
admin_app.add_middleware(
CORSMiddleware,
allow_origins=["https://admin.example.com"],
allow_methods=["GET", "POST", "PUT", "DELETE", "PATCH"],
allow_headers=["Content-Type", "Authorization"],
max_age=3600,
)
app.mount("/api/public", public_app)
app.mount("/api/admin", admin_app)
Django
# settings.py
CORS_ALLOWED_ORIGINS = [
"https://app.example.com",
]
CORS_ALLOW_METHODS = [
'GET',
'POST',
'PUT',
'PATCH',
'DELETE',
'OPTIONS',
]
# Custom middleware for different endpoints
class CustomCORSMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
response = self.get_response(request)
# Read-only for public API
if request.path.startswith('/api/public'):
response['Access-Control-Allow-Methods'] = 'GET, HEAD, OPTIONS'
# Full CRUD for authenticated API
elif request.path.startswith('/api'):
response['Access-Control-Allow-Methods'] = 'GET, POST, PUT, DELETE, PATCH, OPTIONS'
return response
```nginx
### Nginx
```nginx
server {
listen 80;
server_name api.example.com;
# Public read-only API
location /api/public/ {
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, HEAD, OPTIONS';
add_header 'Access-Control-Max-Age' 86400;
return 204;
}
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, HEAD, OPTIONS';
proxy_pass http://backend;
}
# Admin full CRUD API
location /api/admin/ {
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' 'https://admin.example.com';
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, PATCH, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type';
add_header 'Access-Control-Max-Age' 3600;
return 204;
}
add_header 'Access-Control-Allow-Origin' 'https://admin.example.com';
proxy_pass http://backend;
}
}
Best Practices
For Servers
1. Be specific about allowed methods
# ❌ Too permissive
Access-Control-Allow-Methods: *
# ✅ Specific methods only
Access-Control-Allow-Methods: GET, POST, DELETE
```text
**2. Use different methods for different endpoints**
```javascript
// Public endpoints - read only
app.use(
'/api/public',
corsMiddleware({
methods: ['GET', 'HEAD', 'OPTIONS']
})
)
// Authenticated endpoints - full CRUD
app.use(
'/api/users',
corsMiddleware({
methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS']
})
)
3. Always include OPTIONS
# ✅ Include OPTIONS for preflight
Access-Control-Allow-Methods: GET, POST, OPTIONS
```javascript
**4. Validate methods in actual requests**
```javascript
app.use((req, res, next) => {
const allowedMethods = ['GET', 'POST', 'PUT', 'DELETE']
if (!allowedMethods.includes(req.method) && req.method !== 'OPTIONS') {
return res.status(405).send('Method Not Allowed')
}
next()
})
5. Cache preflight responses
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Max-Age: 86400
```javascript
### For Clients
**1. Handle method restrictions gracefully**
```javascript
async function deletePost(id) {
try {
const response = await fetch(`https://api.example.com/posts/${id}`, {
method: 'DELETE',
headers: {
Authorization: 'Bearer token123'
}
})
if (!response.ok) {
throw new Error('Delete failed')
}
} catch (error) {
if (error.message.includes('CORS')) {
console.error('DELETE method not allowed by CORS')
// Fall back to alternative approach
}
}
}
2. Check allowed methods before sending
// Send preflight first to check
const checkMethods = async () => {
const response = await fetch('https://api.example.com/data', {
method: 'OPTIONS',
headers: {
'Access-Control-Request-Method': 'DELETE'
}
})
const allowedMethods = response.headers.get('Access-Control-Allow-Methods')
return allowedMethods.includes('DELETE')
}
```text
## HTTP Methods Overview
### Safe Methods (Read-Only)
```http
Access-Control-Allow-Methods: GET, HEAD, OPTIONS
- GET - Retrieve data
- HEAD - Get headers only
- OPTIONS - Preflight check
Idempotent Methods
Access-Control-Allow-Methods: GET, PUT, DELETE
```text
- Same request multiple times = same result
### Non-Idempotent Methods
```http
Access-Control-Allow-Methods: POST, PATCH
- POST - Create resources (multiple calls = multiple resources)
- PATCH - Partial updates
Common Method Combinations
Read-Only API
Access-Control-Allow-Methods: GET, HEAD, OPTIONS
```text
### Create and Read Only
```http
Access-Control-Allow-Methods: GET, POST, OPTIONS
Full REST API
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
```text
### REST with Partial Updates
```http
Access-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETE, OPTIONS
Testing Access-Control-Allow-Methods
Using curl
# Send preflight request
curl -X OPTIONS https://api.example.com/posts \
-H "Origin: https://app.example.com" \
-H "Access-Control-Request-Method: DELETE" \
-v
# Check response for Access-Control-Allow-Methods header
```javascript
### Using JavaScript
```javascript
// This will trigger preflight for DELETE
fetch('https://api.example.com/posts/123', {
method: 'DELETE',
headers: {
Authorization: 'Bearer token123'
}
})
.then((response) => console.log('Success'))
.catch((error) => console.error('CORS error:', error))
// Manually check allowed methods
fetch('https://api.example.com/posts', {
method: 'OPTIONS'
}).then((response) => {
const methods = response.headers.get('Access-Control-Allow-Methods')
console.log('Allowed methods:', methods)
})
Browser DevTools
1. Open Network tab
2. Look for OPTIONS request (preflight)
3. Check Response Headers for Access-Control-Allow-Methods
4. Verify your method is in the list
Related Headers
- Access-Control-Allow-Origin - Specifies allowed origins
- Access-Control-Allow-Headers - Specifies allowed headers
- Access-Control-Request-Method - Client requests permission for method
- Access-Control-Max-Age - How long to cache preflight response
Frequently Asked Questions
What is Access-Control-Allow-Methods?
This CORS header lists HTTP methods allowed for cross-origin requests. It is sent in preflight responses to tell browsers which methods are permitted.
Which methods need to be listed?
Simple methods (GET, HEAD, POST) work without listing. List methods like PUT, DELETE, PATCH that trigger preflight. Example: Access-Control-Allow-Methods: GET, POST, PUT, DELETE.
Can I use wildcard for methods?
Yes, Access-Control-Allow-Methods: * allows all methods, but only without credentials. With credentials, you must list specific methods.
Why is my PUT/DELETE request failing?
These methods trigger preflight. Ensure your OPTIONS response includes Access-Control-Allow-Methods listing PUT or DELETE. The preflight must succeed before the actual request.