CORS middleware for Remix. It adds standard CORS response headers to Fetch API servers and can either short-circuit preflight requests or pass them through to app-defined OPTIONS handlers.
- Preflight Handling - Automatically handles
OPTIONSpreflight requests - Flexible Origin Rules - Supports static, regex, list, and function-based origin policies
- Credential Support - Supports credentialed requests with spec-safe origin reflection
- Header Controls - Configure allowed and exposed headers, preflight methods, and max age
- Private Network Support - Optionally allow private network preflight requests
npm i remiximport { createRouter } from 'remix/fetch-router'
import { cors } from 'remix/cors-middleware'
let router = createRouter({
middleware: [
cors({
origin: ['https://app.example.com', 'https://admin.example.com'],
credentials: true,
exposedHeaders: ['X-Request-Id'],
}),
],
})
router.get('/api/projects', () => {
return Response.json([{ id: 'p1', name: 'Remix' }], {
headers: {
'X-Request-Id': 'req_123',
},
})
})origin supports:
'*'to allow all originsstringfor a single exact originRegExpfor pattern-based matchingArray<string | RegExp>for multiple exact and pattern matchestrueto reflect the request origin(origin, context) => boolean | stringfor dynamic policies
let router = createRouter({
middleware: [
cors({
origin: ['https://app.example.com', 'https://admin.example.com'],
credentials: true,
}),
],
})let router = createRouter({
middleware: [
cors({
origin(origin, context) {
if (context.url.pathname.startsWith('/public/')) {
return '*'
}
return origin.endsWith('.trusted.example')
},
}),
],
})By default, preflight requests are short-circuited with status 204.
let router = createRouter({
middleware: [
cors({
methods: ['GET', 'POST', 'PATCH'],
allowedHeaders: ['Authorization', 'Content-Type'],
maxAge: 600,
}),
],
})Use a function-based allowedHeaders policy when the header allowlist depends on the request:
let router = createRouter({
middleware: [
cors({
allowedHeaders(request) {
let requestedHeaders = request.headers.get('Access-Control-Request-Headers')
if (requestedHeaders?.includes('x-admin-token')) {
return ['Authorization', 'Content-Type', 'X-Admin-Token']
}
return ['Authorization', 'Content-Type']
},
}),
],
})Function-based allowedHeaders responses vary on Access-Control-Request-Headers, so caches do not reuse a preflight response for a different requested-header set.
Set preflightContinue: true to let downstream handlers process preflight requests. Use preflightStatusCode when you want short-circuited preflight responses to return a status other than 204.
let router = createRouter({
middleware: [
cors({
allowPrivateNetwork: true,
}),
],
})When allowPrivateNetwork is enabled, the middleware adds Access-Control-Allow-Private-Network: true for preflight requests that ask for private network access.
let router = createRouter({
middleware: [
cors({
exposedHeaders: ['X-Request-Id', 'X-Trace-Id'],
}),
],
})- CORS is primarily a browser enforcement mechanism. Disallowed non-preflight requests still reach your handlers unless you add separate request validation.
- When
credentials: trueis used withorigin: '*', the middleware reflects the request origin and addsVary: Originso the response stays cache-safe. - When
allowedHeadersis a function, preflight responses vary onAccess-Control-Request-Headersso caches do not reuse a response for a different requested-header set. preflightContinueandpreflightStatusCodeonly affect how preflightOPTIONSrequests are handled. They do not change actual request authorization.
cop-middleware- Browser-origin protection middleware for unsafe cross-origin requestsfetch-router- Router for the web Fetch APIheaders- Typed HTTP header utilities
See LICENSE