---
title: How can I enable CORS on Vercel?
description: Learn how to add CORS headers to your application on Vercel.
url: /kb/guide/how-to-enable-cors
canonical_url: "https://vercel.com/kb/guide/how-to-enable-cors"
published: 2025-11-03
last_updated: 2025-11-10
authors: Lee Robinson
related:
  - /docs/functions
  - /docs/routing-middleware/api
  - /docs/deployment-protection/methods-to-protect-deployments/vercel-authentication
  - /docs/deployment-protection/methods-to-bypass-deployment-protection/options-allowlist
install_vercel_plugin: npx plugins add vercel/vercel-plugin
---

Cross-Origin Resource Sharing (CORS) is an HTTP‑header mechanism that lets a browser ask another origin for permission to read its responses. It softens the browser’s same‑origin policy so frontend code at `https://app.example` can fetch from `https://api.example` or any other domain when the server explicitly allows it.

## Headers you typically need (and why)

- **Access-Control-Allow-Origin**: which origins may read the response. Use `*` for public APIs or a specific origin such as `https://app.example` for stricter security.
  
- **Access-Control-Allow-Methods**: HTTP verbs the client may use (e.g. `GET, POST, PUT, PATCH, DELETE, OPTIONS`).
  
- **Access-Control-Allow-Headers**: custom request headers the browser may send, such as `Authorization`, `Content-Type`, or `X-CSRF-Token`.
  
- **Access-Control-Allow-Credentials**: whether the browser may send cookies or HTTP‑auth headers. Must be `true` _and_ used together with an explicit (non‑`*`) origin.
  
- **Access-Control-Max-Age**: how long the browser can cache the pre‑flight response, in seconds (e.g. `86400` for 24 h).
  

Browsers issue an **OPTIONS** pre‑flight request whenever a request is “non‑simple” (has custom headers, a non‑GET/POST verb, etc.). The pre‑flight must return all of the headers above or the real request is never sent.

## Implementation patterns on Vercel

[Vercel Functions](https://vercel.com/docs/functions), when used standalone or through frameworks, do not add CORS headers automatically. If you are seeing CORS errors, here's how you can fix it.

### Next.js Route Handler (App Router)

```javascript
const ALLOWED_ORIGIN =
  process.env.NODE_ENV === 'production'
    ? 'https://app.example'
    : '*';

export async function OPTIONS() {
  return new Response(null, {
    status: 200,
    headers: {
      'Access-Control-Allow-Origin': ALLOWED_ORIGIN,
      'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
      'Access-Control-Allow-Headers': 'Content-Type, Authorization',
      'Access-Control-Allow-Credentials': 'true',
    },
  });
}

export async function GET() {
  return Response.json({ ok: true }, {
    headers: { 'Access-Control-Allow-Origin': ALLOWED_ORIGIN },
  });
}
```

### Standalone Vercel Functions

This same code example works for standalone Vercel Functions without a framework, placed at `api/hello.ts`.

### Framework‑level headers

You can also apply headers through different configuration patterns in frameworks:

- **Next.js** : add a `headers()` async function in `next.config.ts` that matches `/api/:path*` and sets the five headers
  
- **SvelteKit** : set headers in the global `handle` hook
  
- **Remix**: return `json(data, { headers })` from loaders or actions
  
- **Nuxt**: use `routeRules` or set headers in `server/api/*`
  

Each method ends up setting the same headers as the examples above.

### Global route headers via `vercel.json`

```json
{
  "headers": [
    {
      "source": "/api/(.*)",
      "headers": [
        { "key": "Access-Control-Allow-Origin", "value": "https://app.example" },
        { "key": "Access-Control-Allow-Methods", "value": "GET, POST, OPTIONS" },
        { "key": "Access-Control-Allow-Headers", "value": "Content-Type, Authorization" },
        { "key": "Access-Control-Allow-Credentials", "value": "true" }
      ]
    }
  ]
}
```

Placing headers in `vercel.json` pushes them to Vercel’s CDN so they apply before your function runs.

## Using Routing Middleware for Dynamic CORS Headers

For more advanced CORS scenarios where you need dynamic header values based on request properties (like origin, user agent, or geolocation), you can use Vercel’s [Routing Middleware](https://vercel.com/docs/routing-middleware/api#continuing-the-routing-middleware-chain). This approach is particularly useful when you need to:

- Set different CORS policies based on the requesting origin
  
- Apply CORS headers conditionally based on request properties
  
- Implement more complex CORS logic that goes beyond static configuration
  

### Basic Dynamic CORS with Middleware

Create a `middleware.ts` file at the root of your project:

```javascript
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

export const config = {
  matcher: '/api/:path*', // Apply only to API routes
};

export default function middleware(request: NextRequest) {
  const origin = request.headers.get('origin');
  
  // Define allowed origins dynamically
  const allowedOrigins = process.env.NODE_ENV === 'production' 
    ? ['https://app.example.com', 'https://admin.example.com']
    : ['http://localhost:3000', 'http://localhost:3001'];
  
  const isAllowedOrigin = origin && allowedOrigins.includes(origin);
  
  // Handle preflight requests
  if (request.method === 'OPTIONS') {
    return new Response(null, {
      status: 200,
      headers: {
        'Access-Control-Allow-Origin': isAllowedOrigin ? origin : 'null',
        'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
        'Access-Control-Allow-Headers': 'Content-Type, Authorization',
        'Access-Control-Allow-Credentials': 'true',
        'Access-Control-Max-Age': '86400',
      },
    });
  }
  
  // Continue with the request and add CORS headers to the response
  const response = NextResponse.next();
  
  if (isAllowedOrigin) {
    response.headers.set('Access-Control-Allow-Origin', origin);
    response.headers.set('Access-Control-Allow-Credentials', 'true');
  }
  
  return response;
}
```

### Geolocation-Based CORS

You can also use Vercel’s geolocation data to implement region-specific CORS policies:

```javascript
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

export const config = {
  matcher: '/api/:path*',
};

export default function middleware(request: NextRequest) {
  const origin = request.headers.get('origin');
  const country = request.geo?.country || 'US';
  
  // Different CORS policies based on country
  const getCorsPolicy = (country: string) => {
    switch (country) {
      case 'US':
      case 'CA':
        return {
          allowedOrigins: ['https://us.example.com', 'https://ca.example.com'],
          allowCredentials: true,
        };
      case 'GB':
      case 'DE':
        return {
          allowedOrigins: ['https://eu.example.com'],
          allowCredentials: true,
        };
      default:
        return {
          allowedOrigins: ['https://global.example.com'],
          allowCredentials: false,
        };
    }
  };
  
  const corsPolicy = getCorsPolicy(country);
  const isAllowedOrigin = origin && corsPolicy.allowedOrigins.includes(origin);
  
  if (request.method === 'OPTIONS') {
    return new Response(null, {
      status: 200,
      headers: {
        'Access-Control-Allow-Origin': isAllowedOrigin ? origin : 'null',
        'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
        'Access-Control-Allow-Headers': 'Content-Type, Authorization',
        'Access-Control-Allow-Credentials': corsPolicy.allowCredentials.toString(),
        'Access-Control-Max-Age': '86400',
      },
    });
  }
  
  const response = NextResponse.next();
  
  if (isAllowedOrigin) {
    response.headers.set('Access-Control-Allow-Origin', origin);
    response.headers.set('Access-Control-Allow-Credentials', corsPolicy.allowCredentials.toString());
  }
  
  return response;
}
```

### Combining with API Route Handlers

When using middleware for CORS, your API route handlers become simpler since the CORS headers are handled at the middleware level:

```javascript
export async function GET() {
  // No need to set CORS headers here - middleware handles it
  return Response.json({ users: [] });
}

export async function POST(request: Request) {
  const data = await request.json();
  // Process the data
  return Response.json({ success: true });
}
```

## Handling the OPTIONS pre‑flight with Deployment Protection

If you have Deployment Protection turned on your preview or production Vercel deployments, you can use OPTIONS Allowlist to allow CORS to work on a list of paths that you define.

- When [**Vercel Authentication**](https://vercel.com/docs/deployment-protection/methods-to-protect-deployments/vercel-authentication), Password Protection, or Trusted IPs is active, unauthenticated pre‑flight requests would normally be blocked.
  
- [**OPTIONS Allowlist**](https://vercel.com/docs/deployment-protection/methods-to-bypass-deployment-protection/options-allowlist) lets you exempt specific paths from Deployment Protection _only_ for `OPTIONS` requests. `/api/*` is on the allowlist by default for new projects.
  

Here's an example of the typical flow:

1. Keep **Vercel Authentication** enabled for `/api/*`.
   
2. Ensure `/api` is in the **OPTIONS Allowlist** (default).
   
3. Browser pre‑flight succeeds; the real `POST /api/...` request still requires auth.
   

## Test your setup

```bash
# Pre‑flight
curl -i -X OPTIONS https://your-domain.vercel.app/api/hello \
  -H "Origin: https://app.example" \
  -H "Access-Control-Request-Method: POST"


<!-- docsgraph:related -->
## Related pages

> **For AI agents:** Follow these links to understand how this page connects to the rest of the Vercel ecosystem. For the full cross-link map (inbound, outbound, prerequisites, and semantic neighbors), see the .graph.md link below.

- [Building Integrations with Vercel REST API](https://vercel.com/docs/integrations/create-integration/vercel-api-integrations?from=related) — Learn how to use Vercel REST API to build your integrations and work with redirect URLs.
- [CDN Cache](https://vercel.com/docs/caching/cdn-cache?from=related) — Learn how Vercel's CDN cache stores your content across a global network to reduce latency and origin load.
- [Node.js](https://vercel.com/docs/functions/runtimes/node-js?from=related) — Learn how to use the Node.js runtime to create functions and deploy Node.js servers on Vercel.
- [Automated & Agent Access](https://vercel.com/docs/deployment-protection/automated-agent-access?from=related) — Grant AI agents, CI/CD pipelines, MCP servers, and testing tools access to Vercel deployments that have Deployment Prote
- [Cache-Control Headers](https://vercel.com/docs/caching/cache-control-headers?from=related) — Learn about the cache-control headers sent to each Vercel deployment and how to use them to control the caching behavior
- [The complete guide to authentication on Vercel](https://vercel.com/kb/guide/complete-guide-authentication-vercel?from=related) — Learn how to implement authentication in your Vercel applications. Covers NextAuth/Auth.js setup, environment variable c
- [Migrate to Vercel from Netlify](https://vercel.com/kb/guide/migrate-to-vercel-from-netlify?from=related) — Migrate your website's configuration from Netlify to Vercel
- [Why is my deployed project giving a 404?](https://vercel.com/kb/guide/why-is-my-deployed-project-giving-404?from=related) — Vercel 404 errors often hit healthy builds when routing metadata does not match the request path. Learn the causes and h
- [Using Vercel as a Standalone CDN](https://vercel.com/kb/guide/using_vercel_as_a_cdn?from=related) — Use Vercel's external rewrites to proxy and cache content from external websites or APIs through Vercel's global edge ne
- [How to Effectively Load Test Your Vercel Application](https://vercel.com/kb/guide/how-to-effectively-load-test-your-vercel-application?from=related) — Learn how to safely load test your Next.js app on Vercel. This guide covers realistic, policy-compliant testing of route

Full cross-link map for this page: [/kb/guide/how-to-enable-cors.graph.md](/kb/guide/how-to-enable-cors.graph.md)
<!-- /docsgraph:related -->

# Simple request
curl -i https://your-domain.vercel.app/api/hello \
  -H "Origin: https://app.example"
```

Look for a `200` status on the OPTIONS call and the correct CORS headers in both responses.

## Common pitfalls and fixes

- **Forgetting to add headers on error paths**: wrap all return points (including 4xx/5xx) in a helper that sets CORS, or apply rules globally through configuration
  
- **Using** `*****` **with** `**Access-Control-Allow-Credentials: true**`: the spec forbids this; send a specific origin instead
  
- **Excessive pre‑flight traffic**: raise `Access-Control-Max-Age` (up to 86400 s = 24 h) so browsers cache the response