CORS 🔌 API

CORS Error: No 'Access-Control-Allow-Origin' header is present

The browser blocked a cross-origin request because the server’s response didn’t include the CORS headers allowing your site’s origin.

Meaning

CORS is enforced by the browser, not the server. Your request often reaches the server and even succeeds — but the browser hides the response from your JavaScript because the server didn’t explicitly allow your origin.

That’s why the same request works in curl or Postman but fails in the browser. The fix is almost always on the server: return the right Access-Control-Allow-* headers, including for the OPTIONS preflight request.

Common causes

  • Server doesn’t send Access-Control-Allow-Origin for your origin
  • Preflight OPTIONS request not handled (returns 404/405/401)
  • Using Access-Control-Allow-Origin: * together with credentials (cookies) — not allowed
  • Custom headers (Authorization, X-Requested-With) not listed in Access-Control-Allow-Headers
  • Error responses (4xx/5xx) skip the CORS middleware, so the real error is hidden
  • Origin mismatch: http vs https, www vs non-www, different port

⚡ Quick fix

  1. Add the CORS headers on the server for your exact origin
  2. Respond to OPTIONS with 204 and the allow headers
  3. If sending cookies, set a specific origin (not *) and Access-Control-Allow-Credentials: true
  4. In development, use your dev server’s proxy instead of calling the API cross-origin
  5. Check the Network tab: if the preflight failed, fix that first

Detailed fix by platform

Node.js

  1. Express with the cors package:
    javascript
    import cors from 'cors';
    app.use(cors({
      origin: ['https://app.example.com', 'http://localhost:5173'],
      credentials: true,
      allowedHeaders: ['Content-Type', 'Authorization'],
    }));

PHP

  1. Plain PHP (before any output):
    php
    $allowed = ['https://app.example.com'];
    $origin = $_SERVER['HTTP_ORIGIN'] ?? '';
    if (in_array($origin, $allowed, true)) {
        header("Access-Control-Allow-Origin: $origin");
        header('Access-Control-Allow-Credentials: true');
        header('Access-Control-Allow-Headers: Content-Type, Authorization');
        header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS');
        header('Vary: Origin');
    }
    if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') { http_response_code(204); exit; }

Nginx

  1. Add headers with always so they’re also sent on errors:
    nginx
    add_header Access-Control-Allow-Origin "https://app.example.com" always;
    add_header Access-Control-Allow-Headers "Content-Type, Authorization" always;
    add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" always;
    if ($request_method = OPTIONS) { return 204; }

React

  1. In development, proxy API calls through the dev server (Vite server.proxy, CRA "proxy" in package.json) so the browser sees a same-origin request.

AWS

  1. S3: add a CORS configuration on the bucket. API Gateway: enable CORS on the resource and redeploy the stage; Lambda proxy integrations must return the headers themselves.

Code examples

Test the preflight from the command line

bash
curl -i -X OPTIONS https://api.example.com/users \
  -H "Origin: https://app.example.com" \
  -H "Access-Control-Request-Method: POST" \
  -H "Access-Control-Request-Headers: content-type, authorization"

The response must include matching Access-Control-Allow-* headers and a 2xx status.

How to diagnose

  1. Network tab — Is the failing request the OPTIONS preflight or the actual request?
  2. Origin — What exact Origin does the browser send?
  3. Response headers — Does the response include Access-Control-Allow-Origin for that origin?
  4. Credentials — Are cookies involved? Then no wildcard origin.
  5. Errors — Is the server actually returning 4xx/5xx without CORS headers?

🧠 Still stuck? Analyze your error

Paste the full message, response headers or stack trace — we'll detect the platform and point to the most likely cause.