- Home
- HTTP Headers
- Access-Control-Request-Method Header
Header
Access-Control-Request-Method Header
Learn how Access-Control-Request-Method tells servers which HTTP method will be used in the actual CORS request. Essential for preflight request handling.
TL;DR: Sent in CORS preflight requests to ask permission for a specific HTTP method. Server must allow the method for the actual request to proceed.
What is Access-Control-Request-Method?
The Access-Control-Request-Method header is sent by browsers during a CORS preflight request to inform the server which HTTP method will be used in the actual cross-origin request. It’s the browser asking: “I want to use this HTTP method - is that allowed?”
This header only appears in OPTIONS preflight requests, not in the actual cross-origin requests.
How Access-Control-Request-Method Works
Browser sends preflight request:
OPTIONS /api/users HTTP/1.1
Host: api.example.com
Origin: https://app.example.com
Access-Control-Request-Method: DELETE
Access-Control-Request-Headers: Authorization
```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, PUT, DELETE
Access-Control-Allow-Headers: Authorization
Access-Control-Max-Age: 3600
Browser then sends the actual request:
DELETE /api/users/123 HTTP/1.1
Host: api.example.com
Origin: https://app.example.com
Authorization: Bearer eyJhbGc...
```text
## Syntax
```http
Access-Control-Request-Method: <method>
Only one HTTP method is specified (no comma-separated list).
# Valid examples
Access-Control-Request-Method: GET
Access-Control-Request-Method: POST
Access-Control-Request-Method: PUT
Access-Control-Request-Method: DELETE
Access-Control-Request-Method: PATCH
```text
## When Preflight is Triggered
Browsers send preflight requests for:
**Non-simple methods:**
- `PUT`
- `DELETE`
- `PATCH`
- `CONNECT`
- `OPTIONS`
- `TRACE`
**Simple methods (no preflight by default):**
- `GET`
- `HEAD`
- `POST` (with simple content-type)
However, even simple methods trigger preflight if custom headers are used.
## Common Examples
### DELETE Request
```http
OPTIONS /api/users/123 HTTP/1.1
Host: api.example.com
Origin: https://app.example.com
Access-Control-Request-Method: DELETE
Access-Control-Request-Headers: Authorization
PUT Request
OPTIONS /api/users/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
```http
### PATCH Request
```http
OPTIONS /api/users/123 HTTP/1.1
Host: api.example.com
Origin: https://app.example.com
Access-Control-Request-Method: PATCH
Access-Control-Request-Headers: Content-Type
POST with Custom Headers
OPTIONS /api/events HTTP/1.1
Host: api.example.com
Origin: https://app.example.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Content-Type, X-Custom-Header
```text
## Real-World Scenarios
### REST API with Full CRUD
**Delete user - Preflight:**
```http
OPTIONS /api/users/456 HTTP/1.1
Host: api.example.com
Origin: https://app.example.com
Access-Control-Request-Method: DELETE
Access-Control-Request-Headers: Authorization
Server response:
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, PATCH
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 3600
```text
**Actual delete request:**
```javascript
fetch('https://api.example.com/users/456', {
method: 'DELETE',
headers: {
Authorization: 'Bearer eyJhbGc...'
}
})
Update Resource with PUT
Client code:
fetch('https://api.example.com/posts/789', {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
Authorization: 'Bearer token123'
},
body: JSON.stringify({
title: 'Updated Title',
content: 'Updated content'
})
})
```http
**Automatic preflight:**
```http
OPTIONS /posts/789 HTTP/1.1
Host: api.example.com
Origin: https://app.example.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: Content-Type, Authorization
Server response:
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 7200
```text
### Partial Update with PATCH
**Client code:**
```javascript
fetch('https://api.example.com/users/123', {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
Authorization: 'Bearer token123'
},
body: JSON.stringify({
email: 'newemail@example.com'
})
})
Preflight request:
OPTIONS /users/123 HTTP/1.1
Host: api.example.com
Origin: https://app.example.com
Access-Control-Request-Method: PATCH
Access-Control-Request-Headers: Content-Type, Authorization
```text
## Server-Side Handling
### Express.js - Method-Specific CORS
```javascript
// Allow specific methods per endpoint
app.options('/api/users/:id', (req, res) => {
res.setHeader('Access-Control-Allow-Origin', 'https://app.example.com')
res.setHeader('Access-Control-Allow-Methods', 'GET, PUT, DELETE')
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization')
res.setHeader('Access-Control-Max-Age', '3600')
res.sendStatus(204)
})
app.options('/api/posts', (req, res) => {
res.setHeader('Access-Control-Allow-Origin', 'https://app.example.com')
res.setHeader('Access-Control-Allow-Methods', 'GET, POST')
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization')
res.setHeader('Access-Control-Max-Age', '3600')
res.sendStatus(204)
})
Dynamic Method Validation
app.options('/api/*', (req, res) => {
const requestedMethod = req.headers['access-control-request-method']
const allowedMethods = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH']
if (!requestedMethod || !allowedMethods.includes(requestedMethod)) {
return res.sendStatus(403)
}
res.setHeader('Access-Control-Allow-Origin', 'https://app.example.com')
res.setHeader('Access-Control-Allow-Methods', allowedMethods.join(', '))
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization')
res.setHeader('Access-Control-Max-Age', '3600')
res.sendStatus(204)
})
```javascript
### Read-Only API
```javascript
// Only allow safe methods
app.options('/api/public/*', (req, res) => {
const requestedMethod = req.headers['access-control-request-method']
const allowedMethods = ['GET', 'HEAD', 'OPTIONS']
if (!allowedMethods.includes(requestedMethod)) {
return res.status(405).send('Method Not Allowed')
}
res.setHeader('Access-Control-Allow-Origin', '*')
res.setHeader('Access-Control-Allow-Methods', allowedMethods.join(', '))
res.setHeader('Access-Control-Max-Age', '86400')
res.sendStatus(204)
})
Role-Based Method Access
app.options('/api/admin/*', (req, res) => {
const origin = req.headers.origin
const requestedMethod = req.headers['access-control-request-method']
// Check if origin has admin access (simplified)
const adminOrigins = ['https://admin.example.com']
const isAdmin = adminOrigins.includes(origin)
const allowedMethods = isAdmin ? ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'] : ['GET']
if (!allowedMethods.includes(requestedMethod)) {
return res.sendStatus(403)
}
res.setHeader('Access-Control-Allow-Origin', origin)
res.setHeader('Access-Control-Allow-Methods', allowedMethods.join(', '))
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization')
res.setHeader('Access-Control-Max-Age', '1800')
res.sendStatus(204)
})
```text
## Best Practices
### 1. Allow Only Necessary Methods
```javascript
// ❌ Too permissive
res.setHeader('Access-Control-Allow-Methods', '*')
// ✅ Specific methods only
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE')
2. Match Methods to Endpoints
// ✅ Different methods for different endpoints
app.options('/api/users', (req, res) => {
res.setHeader('Access-Control-Allow-Methods', 'GET, POST')
// ... other headers
res.sendStatus(204)
})
app.options('/api/users/:id', (req, res) => {
res.setHeader('Access-Control-Allow-Methods', 'GET, PUT, DELETE, PATCH')
// ... other headers
res.sendStatus(204)
})
```javascript
### 3. Validate Requested Method
```javascript
app.options('/api/*', (req, res) => {
const requestedMethod = req.headers['access-control-request-method']
const allowedMethods = ['GET', 'POST', 'PUT', 'DELETE']
if (!requestedMethod) {
return res.sendStatus(400)
}
if (!allowedMethods.includes(requestedMethod)) {
return res.status(405).json({
error: 'Method Not Allowed',
allowed: allowedMethods
})
}
res.setHeader('Access-Control-Allow-Methods', allowedMethods.join(', '))
res.setHeader('Access-Control-Allow-Origin', 'https://app.example.com')
res.sendStatus(204)
})
4. Document Allowed Methods
/**
* POST /api/users
* PUT /api/users/:id
* PATCH /api/users/:id
* DELETE /api/users/:id
*
* CORS: Allows all above methods from https://app.example.com
*/
app.options('/api/users*', (req, res) => {
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE')
// ... other headers
res.sendStatus(204)
})
```text
## Common Errors and Solutions
### Error: Method Not Allowed
```text
Access to fetch at 'https://api.example.com' from origin 'https://app.example.com'
has been blocked by CORS policy: Method DELETE is not allowed by
Access-Control-Allow-Methods in preflight response.
```text
**Solution:**
```javascript
// Add the missing method to Access-Control-Allow-Methods
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE')
Error: Case Sensitivity
// Methods should be uppercase
// ✅ Correct
Access-Control-Request-Method: DELETE
Access-Control-Allow-Methods: GET, POST, DELETE
// ❌ Incorrect
Access-Control-Request-Method: delete
Access-Control-Allow-Methods: get, post, delete
```text
## Testing
### Using curl
```bash
# Test DELETE preflight
curl -X OPTIONS https://api.example.com/users/123 \
-H "Origin: https://app.example.com" \
-H "Access-Control-Request-Method: DELETE" \
-H "Access-Control-Request-Headers: Authorization" \
-v
# Look for response:
# Access-Control-Allow-Methods: GET, POST, PUT, DELETE
# Test PUT preflight
curl -X OPTIONS https://api.example.com/posts/456 \
-H "Origin: https://app.example.com" \
-H "Access-Control-Request-Method: PUT" \
-v
Using JavaScript
// Browser automatically sends preflight for DELETE
fetch('https://api.example.com/users/123', {
method: 'DELETE',
headers: {
Authorization: 'Bearer token123'
}
})
.then((response) => console.log('Deleted'))
.catch((error) => console.error('CORS error:', error))
// Check Network tab for OPTIONS request
// Should see: Access-Control-Request-Method: DELETE
```javascript
### Testing Different Methods
```javascript
// Test each method
const methods = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH']
methods.forEach((method) => {
fetch(`https://api.example.com/users/123`, {
method: method,
headers: {
Authorization: 'Bearer token',
'Content-Type': 'application/json'
}
})
.then(() => console.log(`${method} allowed`))
.catch(() => console.log(`${method} blocked`))
})
Method-Specific Patterns
Idempotent Methods
// PUT and DELETE are idempotent - can be retried safely
const idempotentMethods = ['GET', 'PUT', 'DELETE', 'HEAD', 'OPTIONS']
app.options('/api/users/:id', (req, res) => {
res.setHeader('Access-Control-Allow-Methods', idempotentMethods.join(', '))
// ... other headers
res.sendStatus(204)
})
```javascript
### Safe vs Unsafe Methods
```javascript
// Safe methods (read-only)
const safeMethods = ['GET', 'HEAD', 'OPTIONS']
// Unsafe methods (modify data)
const unsafeMethods = ['POST', 'PUT', 'DELETE', 'PATCH']
// Allow all for admin, only safe for public
const allowedMethods = isAdmin ? [...safeMethods, ...unsafeMethods] : safeMethods
Related Headers
- Access-Control-Allow-Methods - Server response with allowed methods
- Access-Control-Request-Headers - Preflight headers request
- Access-Control-Allow-Origin - Allowed origins
- Access-Control-Allow-Headers - Allowed request headers
- Access-Control-Max-Age - Preflight cache duration
- Origin - Request origin
Frequently Asked Questions
What is Access-Control-Request-Method?
This header is sent in preflight OPTIONS requests to tell the server which HTTP method will be used in the actual request. The server responds with allowed methods.
When is this header sent?
Browsers automatically send it in preflight requests when the actual request uses methods like PUT, DELETE, or PATCH that are not simple methods.
How should servers handle this header?
Check if the requested method is allowed, then respond with Access-Control-Allow-Methods including that method. Return appropriate CORS headers in the OPTIONS response.
What is the difference from Access-Control-Allow-Methods?
Request-Method is sent by the client asking permission. Allow-Methods is sent by the server granting permission. They work together in the preflight handshake.