Tutorial: Set Up CORS for an API
In this tutorial you will add CORS to a Remix API server, configure it for specific origins, handle credentials (cookies), and set up dynamic origin lookup for multi-tenant applications.
Prerequisites
- A Remix project with a working server (see the Getting Started guide).
- Basic understanding of HTTP headers.
Step 1: Allow All Origins (Public API)
If you are building a public API that anyone can consume, start with the simplest configuration:
import { createRouter } from 'remix/fetch-router'
import { cors } from 'remix/cors-middleware'
import { route } from 'remix/fetch-router/routes'
let routes = route({
posts: '/api/posts',
})
let router = createRouter({
middleware: [
cors({ origin: '*' }),
],
})
router.get(routes.posts, () => {
return Response.json([
{ id: 1, title: 'First Post' },
{ id: 2, title: 'Second Post' },
])
})With origin: '*', any website can fetch from your API. The middleware adds Access-Control-Allow-Origin: * to every response.
Step 2: Restrict to Specific Origins
For most applications, you want to allow only your own frontend:
cors({
origin: 'https://myapp.com',
})Or multiple frontends:
cors({
origin: [
'https://myapp.com',
'https://admin.myapp.com',
'http://localhost:5173', // Development
],
})When the request's Origin header matches one of these values, the middleware reflects it back. Otherwise, no CORS headers are set and the browser blocks the request.
Step 3: Handle Credentials (Cookies)
If your frontend sends cookies (for authentication), you must enable credentials and use specific origins:
cors({
origin: 'https://myapp.com',
credentials: true,
})This adds Access-Control-Allow-Credentials: true to responses. The browser will include cookies in cross-origin requests, and your session middleware can read them.
Do not use origin: '*' with credentials
Browsers reject responses that combine Access-Control-Allow-Origin: * with Access-Control-Allow-Credentials: true. Always specify exact origins when using credentials.
Step 4: Configure Allowed Methods and Headers
By default, the middleware allows all standard HTTP methods. You can restrict them:
cors({
origin: 'https://myapp.com',
methods: ['GET', 'POST'],
allowedHeaders: ['Content-Type', 'Authorization'],
exposedHeaders: ['X-Total-Count'],
})methods: Which HTTP methods the client can use.allowedHeaders: Which headers the client can send. Browsers ask about these in preflight requests.exposedHeaders: Which response headers the client-side JavaScript can read. By default, browsers only expose a few safe headers.
Step 5: Cache Preflight Responses
Before making certain cross-origin requests, the browser sends a preflight OPTIONS request to check permissions. You can tell the browser to cache the preflight response so it does not repeat it on every request:
cors({
origin: 'https://myapp.com',
credentials: true,
maxAge: 86400, // Cache preflight for 24 hours
})The maxAge value is in seconds. The middleware responds to preflight requests with a 204 No Content and the appropriate headers. The browser caches this and skips the preflight for subsequent requests within the cache window.
Step 6: Dynamic Origins for Multi-Tenant Apps
For applications with many tenants or subdomains, use a function to determine allowed origins at runtime:
cors({
origin(requestOrigin) {
// Allow any subdomain of myapp.com
if (requestOrigin?.endsWith('.myapp.com')) {
return requestOrigin
}
// Allow known partner domains from config
let partners = getPartnerOrigins() // Your lookup logic
if (requestOrigin && partners.includes(requestOrigin)) {
return requestOrigin
}
return false // Deny all other origins
},
credentials: true,
})The function receives the Origin header value and returns either:
- The allowed origin string (which is set in the response header).
falseto deny the request.
Step 7: Verify CORS Headers
Test your configuration with curl:
# Simulate a cross-origin request
curl -I -H "Origin: https://myapp.com" http://localhost:3000/api/postsLook for these headers in the response:
Access-Control-Allow-Origin: https://myapp.com
Access-Control-Allow-Credentials: true
Vary: OriginTest a preflight request:
curl -X OPTIONS \
-H "Origin: https://myapp.com" \
-H "Access-Control-Request-Method: POST" \
-H "Access-Control-Request-Headers: Content-Type" \
http://localhost:3000/api/postsYou should see a 204 response with Access-Control-Allow-Methods and Access-Control-Allow-Headers.
What You Learned
- How to allow all origins or restrict to specific ones.
- How to enable credentials (cookies) for cross-origin requests.
- How to configure allowed methods, headers, and preflight caching.
- How to use a function for dynamic origin lookup.
- How to verify CORS configuration with
curl.
Next Steps
- API Reference -- Full details on every option.
- cop-middleware -- Cross-origin protection using browser
Sec-Fetch-*headers. - csrf-middleware -- Protect forms from cross-site request forgery.