---
title: How to enable CORS on Vercel
description: Learn how to enable CORS on Vercel with vercel.json, Routing Middleware, framework config, and route handlers, plus how to fix preflight blocked by Deployment Protection.
url: /kb/guide/how-to-enable-cors
canonical_url: "https://vercel.com/kb/guide/how-to-enable-cors"
published: 2025-11-03
last_updated: 2026-08-18
authors: Vercel
related:
  - /docs/functions
  - /docs/project-configuration
  - /docs/routing-middleware
  - /docs/environment-variables/system-environment-variables
  - /docs/routing/rewrites
  - /docs/deployment-protection/methods-to-protect-deployments/vercel-authentication
  - /docs/deployment-protection/methods-to-bypass-deployment-protection/options-allowlist
  - /docs/conformance/rules/no_cors_headers
  - /docs/fluid-compute
install_vercel_plugin: npx plugins add vercel/vercel-plugin
---
<!-- 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.
- [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
- [Request Lifecycle](https://vercel.com/docs/fundamentals/infrastructure?from=related) — Learn how Vercel routes, secures, and serves requests from your users to your application.
- [Application authentication on Vercel](https://vercel.com/kb/guide/application-authentication-on-vercel?from=related) — Secure application authentication on Vercel across layers: proxy checks, the Data Access Layer, PPR-safe rendering, and
- [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
- [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
- [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
- [Migrate a Next.js app from Webflow Cloud to Vercel](https://vercel.com/kb/guide/migrate-a-next-js-app-from-webflow-cloud-to-vercel?from=related) — Move your Next.js app from Webflow Cloud to Vercel: remove the OpenNext Cloudflare adapter, drop the base path, map stor

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 -->


Cross-Origin Resource Sharing (CORS) is an HTTP header mechanism that lets a browser read a response from an origin other than the page it came from. [Vercel Functions](https://vercel.com/docs/functions) don't add CORS headers for you, so a browser request from `https://app.example` to an API on another domain fails until the server sends the right headers. Where you set those headers on Vercel changes what your policy can do, and in some cases the better fix is to skip CORS entirely by proxying the request server-to-server.

## **What is CORS and when do you need it on Vercel?**

CORS softens the browser's same-origin policy so frontend code at `https://app.example` can fetch from `https://api.example` when the server allows it. You need it whenever a browser reads a response across origins, which covers most setups where your frontend and API sit on different domains.

A CORS policy is a set of response headers that tell the browser what's allowed:

- **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 access.
  
- **Access-Control-Allow-Methods:** The HTTP verbs the client may use, such as `GET, POST, PUT, PATCH, DELETE, OPTIONS`.
  
- **Access-Control-Allow-Headers:** The 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. Set it to `true` only with an explicit, non- origin.
  
- **Access-Control-Max-Age:** How long, in seconds, the browser can cache the preflight response, such as `86400` for 24 hours.
  

Browsers send a preflight `OPTIONS` request before requests that use a method other than `GET`, `HEAD`, or `POST`, or that carry custom headers. The preflight has to return these headers, or the real request never runs.

## Where to configure CORS headers on Vercel

You can set CORS headers in several places on Vercel, from static rules at the CDN to runtime logic in middleware. Choose the layer by whether your policy is static, meaning the same headers on every request, or dynamic, meaning the allowed origin depends on the request. Static rules belong in configuration, and runtime decisions belong in middleware or function code.

### Set CORS headers in `vercel.json` (CDN layer)

The [`headers`](https://vercel.com/docs/project-configuration) property in `vercel.json` applies CORS headers at the CDN before your function runs. Use it for static rules across a path pattern:

```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" }
      ]
    }
  ]
}
```

To match one specific origin at this layer, add a `has` condition on the `Origin` header:

```json
{
  "headers": [
    {
      "source": "/(.*)",
      "has": [{ "type": "header", "key": "Origin", "value": "<https://app.example>" }],
      "headers": [
        { "key": "Access-Control-Allow-Origin", "value": "<https://app.example>" },
        { "key": "Access-Control-Allow-Credentials", "value": "true" }
      ]
    }
  ]
}
```

The `vercel.json` layer supports only static values, so it can't validate an origin against an allowlist or switch behavior by environment. For that, use Routing Middleware.

### Set CORS headers in `next.config.ts` (framework layer)

Next.js projects can set the same headers in framework config with an async `headers()` function that mirrors the `vercel.json` shape:

```tsx
import type { NextConfig } from 'next';

const nextConfig: NextConfig = {
  async headers() {
    return [
      {
        source: '/api/:path*',
        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' },
        ],
      },
    ];
  },
};

export default nextConfig;
```

Path matching supports modifiers on named parameters:

- **(zero or more):** Matches `/blog`, `/blog/a`, and `/blog/a/b/c` for a pattern like `/blog/:slug*`.
  
- `**+**` **(one or more):** Matches one or more path segments after the parameter.
  
- `**?**` **(zero or one):** Matches an optional single segment.
  

You can also wrap a regular expression in parentheses after a parameter for finer matching. These values are static like `vercel.json`, so use middleware when the allowed origin depends on the request.

### Validate origins at runtime with Routing Middleware

When the allowed origin depends on the request, such as an environment-specific allowlist or several allowed origins, validate it in [Routing Middleware](https://vercel.com/docs/routing-middleware). Middleware runs on Vercel's network before the request reaches your function, so it can answer a preflight `OPTIONS` before your function starts. Add a `middleware.ts` file at the root of your project:

```tsx
import { NextRequest, NextResponse } from 'next/server';

const allowedOrigins =
  process.env.NODE_ENV === 'production'
    ? ['<https://app.example>']
    : ['<http://localhost:3000>'];

export function middleware(request: NextRequest) {
  const origin = request.headers.get('origin');
  const isAllowedOrigin = origin !== null && allowedOrigins.includes(origin);

  if (request.method === 'OPTIONS') {
    return new Response(null, {
      status: 200,
      headers: {
        ...(isAllowedOrigin ? { 'Access-Control-Allow-Origin': origin } : {}),
        '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',
        Vary: 'Origin',
      },
    });
  }

  const response = NextResponse.next();
  if (isAllowedOrigin) {
    response.headers.set('Access-Control-Allow-Origin', origin);
    response.headers.set('Vary', 'Origin');
  }
  return response;
}

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

The `matcher` values must be constants so Vercel can resolve them at build time, and any dynamic value like a variable is ignored.

Preview deployments get a new URL on every push, so a hardcoded production origin fails CORS on previews. Drive `allowedOrigins` from an environment variable, and use the `VERCEL_PROJECT_PRODUCTION_URL` [system environment variable](https://vercel.com/docs/environment-variables/system-environment-variables) for your production domain that stays set even during preview deployments. For a single endpoint, you can set the same headers directly in a route handler.

### Set CORS headers in a route handler (function layer)

In the Next.js App Router, a route handler sets CORS headers with standard Web APIs, which works well when one endpoint needs its own policy:

```tsx
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',
    },
  });
}

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

This pattern fits a single endpoint well. To apply CORS across many handlers without repeating code, use `vercel.json`, framework config, or middleware instead.

## How to skip CORS on Vercel with a rewrite proxy

Before configuring any headers, check whether you need them at all. A [rewrite](https://vercel.com/docs/routing/rewrites) can proxy a browser request to an external origin server-to-server, so the browser sees a same-origin response and never sends a preflight:

```json
{
  "rewrites": [
    { "source": "/api/:path*", "destination": "<https://external-api.com/:path*>" }
  ]
}
```

These rules run at the routing layer before the request reaches a function, and they apply when you deploy the change. This helps when you integrate a third-party service that doesn't handle preflight `OPTIONS` requests well, because the browser-facing preflight goes away.

Server Components and Server Actions avoid CORS in the same way. A request made on the server isn't subject to the browser's same-origin policy, so fetching data in a Server Component removes the cross-origin problem from the browser path.

## How to fix CORS preflight blocked by Deployment Protection

When [Deployment Protection](https://vercel.com/docs/deployment-protection/methods-to-protect-deployments/vercel-authentication) through Vercel Authentication, Password Protection, or Trusted IPs is on, an unauthenticated preflight `OPTIONS` request gets a `401` from Vercel before your middleware or function runs. The browser never sees your CORS headers, so the actual request is never sent.

The [OPTIONS Allowlist](https://vercel.com/docs/deployment-protection/methods-to-bypass-deployment-protection/options-allowlist) fixes this case. It exempts specific paths from Deployment Protection for `OPTIONS` requests only, matching by path prefix, so adding `/api` covers `/api/v1/users` and every path under it. For new projects, `/api/*` is on the allowlist by default.

Verify the fix with `curl` against your deployment:

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

Look for a `200` status and the expected CORS headers in the response. A `401` means the path isn't on the allowlist yet.

## Common CORS mistakes and security gotchas on Vercel

A working CORS setup can still break, usually from caching, credentials, or middleware behavior.

### Cache poisoning from origin-dependent CORS

When your policy returns a different `Access-Control-Allow-Origin` per origin, CDN caching can serve a response approved for one origin to a request from another, and the browser blocks it. This happens because the `Origin` request header isn't part of the CDN cache key by default. Add a `Vary: Origin` header so the cache keys on the origin, and set `Cache-Control: no-cache` on responses whose CORS headers vary by origin. A single static origin or `*` stays safe to cache, because the value doesn't change per request.

### Wildcards, credentials, and the null origin

`Access-Control-Allow-Origin: *` is fine for public, unauthenticated APIs. It becomes a problem only alongside `Access-Control-Allow-Credentials: true`, a combination browsers already reject. Anyone can already reach public data with `curl` or any server-side HTTP client, since CORS is a browser-enforced policy rather than an authorization mechanism.

The `null` origin is more dangerous. Some setups allowlist `null` for local or sandboxed contexts, but an attacker can send `Origin: null` from a sandboxed iframe. If your server reflects `null` with credentials allowed, it's as exposed as a wildcard, so don't allowlist `null` origins.

When you need credentials, `Access-Control-Allow-Origin` must name one exact origin. To support several credentialed origins, validate the incoming `Origin` against your allowlist in middleware, echo back the matched origin, and set `Vary: Origin`. Anchor any subdomain regex to the end of the string, or a host like `attacker.com.example.com` can slip past a loose pattern.

On Enterprise plans, the [Conformance](https://vercel.com/docs/conformance/rules/no_cors_headers) rule `NO_CORS_HEADERS` flags CORS configuration for review. It catches values such as `Access-Control-Allow-Origin: '*'` and `Access-Control-Allow-Credentials: true` so a reviewer can confirm the policy is intentional.

### **Middleware limits: Server Actions and cold-start preflight**

Middleware is the most flexible CORS layer on Vercel, but a few cases need handling:

- **Server Action conflicts:** Returning a response from middleware breaks Next.js Server Actions. Check for the action request first with `request.headers.get('next-action')`, then let those requests pass through instead of returning early.
  
- **Cold-start preflight timeouts:** A preflight that hits a cold function can time out before the CORS headers return, which the browser shows as a CORS error. Handle preflight in middleware, or enable [Fluid compute](https://vercel.com/docs/fluid-compute) so warm instances keep the handler responsive.
  
- **Authentication belongs in your function;** use middleware for lightweight tasks like CORS headers and proxy rewrites. Keep authentication and authorization in function or application code.
  

With those cases handled, middleware covers the runtime CORS logic that static configuration can't.

## Next steps

With your CORS policy in place, deploy your project and verify the preflight and the real request against your live URL. [Start a new project](https://vercel.com/new) or [browse the templates](https://vercel.com/templates) to build on Vercel.

## Related resources

- [Vercel Functions](https://vercel.com/docs/functions)
  
- [Routing Middleware](https://vercel.com/docs/routing-middleware)
  
- [OPTIONS Allowlist](https://vercel.com/docs/deployment-protection/methods-to-bypass-deployment-protection/options-allowlist)
  
- [System environment variables](https://vercel.com/docs/environment-variables/system-environment-variables)
  
- [Project configuration](https://vercel.com/docs/project-configuration)
  
- [Conformance rules](https://vercel.com/docs/conformance/rules/no_cors_headers)
  

## Frequently asked questions

### Do Vercel Functions add CORS headers automatically?

No. Vercel Functions don't add CORS headers for you, whether you run them standalone or through a framework. Every function type needs an explicit CORS policy, set in the function code, `vercel.json`, your framework config, or Routing Middleware. Without those headers, a browser blocks cross-origin reads of the response.

### _Can I use Access-Control-Allow-Origin: \\ with credentials?_

No. Browsers reject any response that combines `Access-Control-Allow-Origin: *` with `Access-Control-Allow-Credentials: true`. When a request requires credentials, the origin must be exactly one value. Validate the incoming `Origin` header against your allowlist in middleware, echo back the matched origin, and add a `Vary: Origin` header.

### Why does my CORS work in production but fail in preview deployments?

Preview deployments get a new URL on every push. If your allowlist only includes your production domain, preview requests fail the origin check. Drive your allowed origins from an environment variable, and reference the `VERCEL_PROJECT_PRODUCTION_URL` system environment variable, which stays set even on preview deployments, to detect the deployment context.

### How do I handle CORS for streaming (SSE) responses on Vercel?

Set CORS headers on the initial response before streaming starts. Send `Access-Control-Allow-Origin` alongside `Content-Type: text/event-stream` and `Cache-Control: no-cache` in the response headers. If middleware compresses the stream, adding `Content-Encoding: none` resolves Server-Sent Events compatibility issues so the browser reads the stream.