What is CORS? How cross-origin requests work and how to fix CORS errors
Published on November 9, 2026 11 min read
CORS lets a server decide which websites may read its responses. Learn how it works, what each CORS error means and how to enable CORS on your hosting.
CORS (Cross-Origin Resource Sharing) is the browser mechanism that lets a server decide which other websites may read its responses. By default, a script on one site cannot read data from another origin; the server grants that permission with response headers such as Access-Control-Allow-Origin. A CORS error means the server did not send a matching permission, so the fix belongs on the server that delivers the data, not in the page that asks for it.
What is CORS?
Every web page has an origin: the scheme (https), the host name (www.yourdomain.com) and the port. Browsers keep pages with different origins apart under the same-origin policy. A request from one origin to another is a cross-origin request, and CORS is the protocol a server uses to allow it anyway.
Because all three parts count, small differences matter. Seen from a page on https://www.yourdomain.com:
| URL | Same origin? | Why |
|---|---|---|
https://www.yourdomain.com/contact | Yes | Only the path differs |
http://www.yourdomain.com | No | Different scheme (http instead of https) |
https://yourdomain.com | No | Different host: no www |
https://shop.yourdomain.com | No | Different host: a subdomain |
https://www.yourdomain.com:8443 | No | Different port |
https://api.other-service.com | No | Different domain |
So your own subdomain, or the version of your site without www, is a different website as far as the browser is concerned. Many CORS problems start right there.
Why does the browser block cross-origin requests?
The same-origin policy protects visitors. When you are logged in to your webmail or your bank, the browser sends your session cookie with every request to that site. Without the policy, a script on any other website could quietly request your inbox or balance and read the result. The browser therefore withholds the response from that script unless the server says otherwise with CORS headers.
Keep in mind that CORS is enforced by the browser; it does not lock down your server. curl, mobile apps and other servers ignore it, and for a GET or POST without a preflight the request usually reaches your server anyway; the browser only hides the response. Your server still has to check who is logged in and what they are allowed to do.
CORS applies to these requests:
- JavaScript requests made with
fetch()orXMLHttpRequest, for example to an API; - web fonts loaded in CSS with
@font-face; - JavaScript modules (
<script type="module">); - images and video drawn to a
canvasor used in WebGL, and elements with thecrossoriginattribute.
A plain <img>, a stylesheet or a <script src> from another domain loads without CORS, and so do links, form submissions and iframes such as a YouTube or Vimeo embed.
How does CORS work?
For a request that needs no preflight, such as fetching data with GET, the flow is:
- A script on
https://www.yourdomain.comrequests data fromhttps://api.other-service.com. - The browser adds the header
Origin: https://www.yourdomain.com. - The server responds and includes
Access-Control-Allow-Origin: https://www.yourdomain.comif it allows your site (or*to allow everyone). - The browser compares that value with the page's origin. If they match, the script gets the response. If not, the browser blocks it and logs a CORS error in the console.
Preflight requests
For requests that can change data, the browser first asks permission with a separate preflight request using the OPTIONS method. That happens for PUT, PATCH and DELETE, for custom headers such as Authorization or X-API-Key, and for a POST with Content-Type: application/json. Only GET, HEAD and POST with application/x-www-form-urlencoded, multipart/form-data or text/plain and standard headers skip the preflight.
The preflight looks like this:
OPTIONS /orders HTTP/1.1
Host: api.other-service.com
Origin: https://www.yourdomain.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: content-type, authorization
The server must answer with a success status (200 or 204) and the headers that allow the request:
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://www.yourdomain.com
Access-Control-Allow-Methods: GET, POST
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 7200
Only then does the browser send the actual request. Access-Control-Max-Age lets the browser cache the preflight result. Without it the cache lasts 5 seconds; Chrome caps it at 2 hours (7200 seconds) and Firefox at 24 hours.
CORS headers explained
| Header | What it does | Example |
|---|---|---|
Access-Control-Allow-Origin | Which origin may read the response: exactly one origin, or * for any | https://www.yourdomain.com |
Access-Control-Allow-Methods | Allowed methods (preflight response) | GET, POST, PUT |
Access-Control-Allow-Headers | Custom request headers that are allowed (preflight response) | Content-Type, Authorization |
Access-Control-Allow-Credentials | Whether cookies and HTTP authentication may be included | true |
Access-Control-Max-Age | How long the browser may cache the preflight, in seconds | 7200 |
Access-Control-Expose-Headers | Extra response headers the script may read | X-Total-Count |
Vary | Tells caches that the response differs per origin | Origin |
Two rules trip people up. Access-Control-Allow-Origin takes a single value, so a comma-separated list of domains does not work; to allow several sites, your server picks the matching origin per request (see the PHP example below). And when cookies or credentials are included, the server must name the exact origin instead of * and also send Access-Control-Allow-Credentials: true.
How to read a CORS error
For security reasons your script only sees a generic network error; the real reason is shown in the browser's developer tools:
- Open the page and press F12 (Cmd+Option+I on a Mac).
- Open the Console tab to read the error and its reason.
- Open the Network tab and reload. In Chrome the failed request shows CORS error in the Status column. The OPTIONS request that went first appears as a separate row with preflight in the Type column.
Chrome errors always start with Access to fetch at '…' from origin '…' has been blocked by CORS policy: followed by the reason. Firefox writes Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at … with the reason in brackets after Reason:. The first URL is the request that failed, the second is your page's origin.
Common CORS errors and how to fix them
| Error (Chrome) | Cause | Fix |
|---|---|---|
No 'Access-Control-Allow-Origin' header is present on the requested resource. | The server sends no CORS header. An error page, a firewall or a redirect without the header causes the same message. | Add the header on the server that serves the data, and make sure error responses include it too. |
The 'Access-Control-Allow-Origin' header has a value '…' that is not equal to the supplied origin. | The header names a different origin, often with or without www, or http instead of https. | Use the origin exactly as it appears in the error, including https and www. |
The 'Access-Control-Allow-Origin' header contains multiple values '…', but only one is allowed. | The header is set in two places (for example .htaccess and a plugin or your code), or it contains a list. | Set it in one place. For several sites, let your code return one origin per request. |
The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'. | The request includes cookies or credentials, but the server answers with *. | Return the exact origin and add Access-Control-Allow-Credentials: true. |
Response to preflight request doesn't pass access control check: It does not have HTTP ok status. | The OPTIONS request gets an error such as 401, 403, 404 or 500, often because the server requires a login for OPTIONS too or a security plugin blocks it. | Let OPTIONS through without authentication and answer with 200 or 204 plus the CORS headers. |
Response to preflight request doesn't pass access control check: Redirect is not allowed for a preflight request. | The URL redirects, for example from http to https, from non-www to www, or to a URL with a trailing slash. | Call the final URL directly, exactly as it appears after the redirect. |
Request header field … is not allowed by Access-Control-Allow-Headers in preflight response. | Your request sends a header the server does not allow, such as Authorization. | Add that header to Access-Control-Allow-Headers. |
Method … is not allowed by Access-Control-Allow-Methods in preflight response. | The method, such as PUT or DELETE, is missing from the server's list. | Add it to Access-Control-Allow-Methods. |
If you open an HTML file straight from your computer (a file:// URL), its origin is null and nearly every CORS request fails. Test through a local web server or on your hosting instead.
The fix always lives on the server that provides the data. A browser extension that disables CORS only works in your own browser, and mode: 'no-cors' in fetch() returns an opaque response whose body and headers your script cannot read. If the API belongs to someone else and you cannot get its headers changed, call it from your own server instead, for example with PHP: CORS does not apply between servers.
How to enable CORS on your hosting
If you own the data another site wants to read, you set the CORS headers on your own website. With LJPc hosting you can do that in the .htaccess file or in your PHP code. Which one fits depends on what you share and on the web server that delivers the file.
Check which server delivers the file
Request the file's headers with curl and include an origin, the way a browser does:
curl -I -H "Origin: https://www.yourdomain.com" https://yourdomain.com/fonts/brand.woff2
Look for two lines in the output:
server: LiteSpeed: headers from .htaccess apply to every file, including fonts and images.server: nginx: on these servers nginx serves static files such as images, fonts, CSS and JavaScript itself, and headers from .htaccess are not added to them. Pages and responses generated by PHP, such as WordPress or your own API, do get them. If a font or image on such a server needs a CORS header, create a support ticket with the file's URL and the site that loads it, and we will look at it with you.
Once access-control-allow-origin appears in the output, your setting works.
Allow web fonts and images with .htaccess
A classic case: your site runs on www.yourdomain.com, but the CSS loads fonts from yourdomain.com or a subdomain. The browser treats that as another origin and refuses the font. The cleanest fix is a relative URL in your CSS (such as /fonts/brand.woff2), so the font comes from the same origin. If that is not possible, add a CORS header to the font files:
- Open the .htaccess file in the httpdocs folder, as described in set up .htaccess: redirects, security and more, and make a copy first.
- Add this block at the top of the file and save it:
<IfModule mod_headers.c>
<FilesMatch "\.(woff2?|ttf|otf|eot)$">
Header set Access-Control-Allow-Origin "*"
</FilesMatch>
</IfModule>
- Check the result with the curl command above.
A * is fine for public files such as fonts and images. If only your own site may use them, replace * with a single origin, for example "https://www.yourdomain.com". To include images, add their extensions to the FilesMatch line, such as png|jpe?g|webp|svg. For other headers in .htaccess, see HTTP security headers: set up HSTS, CSP and more.
Enable CORS in PHP for your own API
To allow several sites, or to answer the preflight yourself, handle CORS in PHP. This example only allows the origins in the list and answers the OPTIONS request straight away:
<?php
$allowed = ['https://www.yourdomain.com', 'https://app.yourdomain.com'];
$origin = $_SERVER['HTTP_ORIGIN'] ?? '';
header('Vary: Origin');
if (in_array($origin, $allowed, true)) {
header('Access-Control-Allow-Origin: ' . $origin);
}
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
header('Access-Control-Allow-Methods: GET, POST');
header('Access-Control-Allow-Headers: Content-Type, Authorization');
header('Access-Control-Max-Age: 7200');
http_response_code(204);
exit;
}
// Your own code follows here, for example:
header('Content-Type: application/json');
echo json_encode(['status' => 'ok']);
Put this at the top of your API's PHP file, before anything else runs. Vary: Origin makes caches store the response per origin. If the other sites use cookies or a login, add header('Access-Control-Allow-Credentials: true'); inside the in_array block. Never reflect any origin together with credentials: that lets every website read the data of your logged-in visitors.
CORS and WordPress
The WordPress REST API (everything under /wp-json/) already sends CORS headers: WordPress echoes the request's origin in Access-Control-Allow-Origin and allows the common methods and headers. For authenticated actions WordPress also requires a valid nonce in the X-WP-Nonce header; without one, it treats the request as logged out. If you still get a CORS error from the REST API, the cause is usually one of these:
- a security plugin that blocks the REST API or OPTIONS requests (the preflight then gets 401 or 403);
- a redirect, because your code calls a URL with http or without www;
- a second CORS header from .htaccess or a plugin on top of the one WordPress sends.
So leave the REST API headers to WordPress and do not add them again in .htaccess. If your site also loads resources over http after moving to https, see fix mixed content and HTTPS errors.
Iframe embed codes: what does referrerpolicy mean?
Embed codes from YouTube or Vimeo contain attributes such as referrerpolicy="strict-origin-when-cross-origin" and allow="autoplay; fullscreen; picture-in-picture; clipboard-write; encrypted-media; web-share". They are not CORS settings: the video plays in its own frame served by the video platform, and your page never needs to read it. Here is what they do:
referrerpolicy="strict-origin-when-cross-origin"controls how much of your page URL the video platform receives. Other sites only get your origin (such ashttps://www.yourdomain.com/) without the path, and an insecure http site gets nothing. It is the browser default, and YouTube recommends it because the player needs to know which site it is embedded on, so keep it. More on this setting under Referrer-Policy in HTTP security headers.allowgrants the frame browser features: autoplay, fullscreen, picture-in-picture, writing to the clipboard (clipboard-write), protected video playback (encrypted-media) and sharing through your device's share menu (web-share).allowfullscreenis the legacy way to allow fullscreen, andframeborder="0"removes the frame border. That attribute is deprecated; in new code, use CSS for the border.
Need help?
Stuck, or not sure which setting suits your site? Create a support ticket with the page URL and the error from the console, and we will look into it with you. You can also call us on +31 85 130 6429.
Frequently asked questions
What does CORS stand for?
CORS stands for Cross-Origin Resource Sharing. It is a browser mechanism that lets a server use headers such as Access-Control-Allow-Origin to say which other origins may read its responses. Without that permission, the browser blocks the response.
How do I fix "No 'Access-Control-Allow-Origin' header is present on the requested resource"?
Add the Access-Control-Allow-Origin header on the server that serves the resource, with the exact origin of your page or * for public files. Also check that error responses and redirects include it, because a 404, a firewall block or a redirect without the header triggers the same error.
How do I enable CORS with .htaccess?
Add Header set Access-Control-Allow-Origin "*" (or one exact origin) inside an IfModule mod_headers.c block, optionally limited with FilesMatch to fonts or images. On LJPc hosting this applies to all files when the response shows server: LiteSpeed; when it shows server: nginx, it only applies to PHP responses.
Is Access-Control-Allow-Origin: * safe?
For public files such as fonts, images or an open API, yes. For data behind a login, no: return the exact origin and send Access-Control-Allow-Credentials: true. Browsers reject the wildcard anyway when cookies or credentials are included.
Can I fix a CORS error in my own JavaScript?
No, the permission has to come from the server. mode: no-cors only gives you an opaque response your script cannot read, and a browser extension only works in your own browser. If you cannot change the server, call the API from your own back end, because CORS does not apply between servers.