# CORS, Cross-Origin Resource Sharing

> Learn how CORS response headers let browser JavaScript read cross-origin responses, how preflight and credentials work, and how to configure Express.

Author: [Flavio Copes](https://flaviocopes.com/about/) | Published: 2018-04-09 | Updated: 2026-07-18 | Topics: [Web Platform](https://flaviocopes.com/tags/platform/) | Canonical: https://flaviocopes.com/cors/

**CORS**, or **Cross-Origin Resource Sharing**, is an HTTP-header mechanism that lets a server choose which other origins may read its responses in a browser.

An origin is the combination of scheme, host, and port. These are different origins:

- `https://app.example.com`
- `https://api.example.com`
- `http://app.example.com`
- `https://app.example.com:8443`

Browser JavaScript normally follows the same-origin policy. A `fetch()` or [XHR](https://flaviocopes.com/xhr/) call can send a cross-origin request, but JavaScript cannot read the response unless the server returns the right CORS headers.

The server controls the policy. You cannot fix a missing CORS header only in frontend JavaScript.

If you're stuck on a CORS error right now, I built a free [CORS debugger](https://flaviocopes.com/tools/cors-debugger/): describe your request and it tells you which headers the server needs to send.

The complete rules live in the [MDN CORS guide](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS).

## What does CORS protect?

CORS controls whether browser JavaScript can **read a response**. It does not stop a request from reaching the server.

This distinction matters.

A form, `curl`, another server, or a script outside the browser can still send a request to your API. CORS is not authentication or authorization. You must still protect private routes and check permissions on the server.

Some cross-origin resources can be embedded without CORS, including many images and classic scripts. Reading those resources from JavaScript is different. Web fonts, JavaScript modules, WebGL textures, and images read through Canvas also use CORS rules.

## Allow a public resource

For a public response that does not use credentials, the server can return:

```http
Access-Control-Allow-Origin: *
```

The wildcard means any origin may read the response in browser JavaScript.

Do not use this for private data. The browser does not know whether a response is sensitive.

## Allow one origin

To allow one frontend, return its exact origin:

```http
Access-Control-Allow-Origin: https://app.example.com
```

If the server chooses the value dynamically from an allowlist, also return:

```http
Vary: Origin
```

This tells caches that the response can change based on the request's `Origin` header. See the [`Access-Control-Allow-Origin` reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Access-Control-Allow-Origin#cors_and_caching).

Never reflect any incoming `Origin` without checking it against an allowlist first.

## Example with Express

For Express, use the official [`cors` middleware](https://expressjs.com/en/resources/middleware/cors/).

This route allows one frontend origin:

```js
const express = require('express')
const cors = require('cors')

const app = express()

const corsOptions = {
  origin: 'https://app.example.com',
}

app.get('/products', cors(corsOptions), (request, response) => {
  response.json([{ id: 1, name: 'Keyboard' }])
})

app.listen(3000)
```

The middleware sets the response headers. It does not block non-browser clients from calling the route.

If the same policy applies to the whole API, register it once:

```js
app.use(cors(corsOptions))
```

Application-level middleware also handles preflight requests for the matching routes.

## How preflight requests work

Some cross-origin requests can be sent without a preflight. They must use `GET`, `HEAD`, or `POST`, and only CORS-safelisted headers.

For `POST`, the allowed `Content-Type` values are:

- `application/x-www-form-urlencoded`
- `multipart/form-data`
- `text/plain`

Even a `GET` can trigger preflight if you add a non-safelisted header such as `Authorization`.

For other requests, the browser first sends an `OPTIONS` request. This asks whether the real method and headers are allowed:

```http
OPTIONS /products HTTP/1.1
Origin: https://app.example.com
Access-Control-Request-Method: DELETE
Access-Control-Request-Headers: authorization
```

The server can approve it with:

```http
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: DELETE
Access-Control-Allow-Headers: Authorization
```

The browser sends the `DELETE` request only after the preflight succeeds.

## CORS with cookies

Fetch does not include cross-origin credentials by default. To send cookies, use:

```js
fetch('https://api.example.com/account', {
  credentials: 'include',
})
```

The server must return both:

```http
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Credentials: true
```

You cannot combine credentials with `Access-Control-Allow-Origin: *`. The browser will block access to that response.

CORS also does not override cookie rules such as `SameSite` and `Secure`.

CORS does not replace CSRF protection. If a route changes data using cookies, protect it against cross-site request forgery too.

## Debugging CORS errors

JavaScript usually receives a generic network error when CORS fails. The browser console contains the useful details.

Check:

- the exact frontend origin
- `Access-Control-Allow-Origin` on the response
- the `OPTIONS` response for preflighted requests
- allowed methods and headers
- credential settings on both client and server

Avoid reaching for `mode: 'no-cors'` as a fix. It gives JavaScript an opaque response whose status, headers, and body cannot be read.
