Cross-Origin Requests and CORS
Sooner or later you will fetch data from another site and see a red error in the Console that mentions CORS, even though the same address opens perfectly well in a new tab. That error is not a bug in your code. It is the browser protecting the person using it, and the fix is almost never in your page. This topic explains what the browser is guarding against, and who can say yes.
An office building with a front desk runs on the same rule. You can walk into your own company's office freely. To get onto another company's floor, that company has to put your name on the visitor list, and shouting at the desk does not change the list. The browser is the desk, your page's own site is your office, and every other site is another company's floor.
What Is an Origin?
The browser decides what belongs together by origin: three parts of an address taken together. The scheme is the part before the colon, http or https. The host is the name, such as example.com or localhost. The port is a number that picks one server program on that computer; it is often left out of addresses, because http and https each have a usual one. Two addresses have the same origin only when all three parts match exactly.
The page: http://localhost:3000/index.html http://localhost:3000/data/projects.json same origin: only the path differs https://localhost:3000/index.html different: https instead of http http://localhost:5500/index.html different: port 5500, not 3000 http://127.0.0.1:3000/index.html different: another host name
Suppose your page is open at localhost, port 3000. Your own preview server's address may say localhost or 127.0.0.1, with its own port number; the address bar tells you which. The first address shares the page's scheme, host and port, and only the path after them differs, so it is the same origin. The second changes the scheme, the third the port. The fourth is the surprise: 127.0.0.1 is another name for the very same computer, but the browser compares the names as written, so it counts as a different origin.
The Same-Origin Policy
The rule the browser enforces is the same-origin policy: a page's script may send requests anywhere, but it may read the answers only from its own origin, unless the other side allows it. A page can still show an image from another site or link to it. What it cannot do by default is have its script read another site's answer.
The reason is you. Your browser holds your signed-in sessions: the small stored values called cookies that keep you logged in to your email, your bank, your work tools. Without this rule, any page you visited could quietly ask your browser to fetch your email's inbox in the background, with your cookies attached, then read the answer and send it anywhere. The same goes for pages on your office network that only your computer can reach. The same-origin policy is why a random page cannot read the other tabs of your life.
CORS: How a Server Says Yes
Many servers want pages on other sites to read their answers: public data, weather, an API made for exactly that. CORS, short for Cross-Origin Resource Sharing, is how they say so. The server adds a header to its answer called Access-Control-Allow-Origin, naming the origin that may read it, or a star meaning any origin. The browser checks that header, and only if it matches does it hand the answer to your script.
Request URL: https://api.github.com/users/octocat/repos?sort=updated&per_page=5 Status code: 200 OK Response headers access-control-allow-origin: * content-type: application/json; charset=utf-8
This is part of what the Network panel shows for a request to GitHub's public list of repositories, the data the last topic of this chapter uses. Click the request, open its Headers tab, and look under Response headers: the allow-origin header is a star. GitHub allows any page, from any origin, to read this answer, which is why a page can use it directly.
Some requests need one step more. When a request uses a less common method, such as DELETE for removing something, or carries extra headers, such as a label saying it sends JSON, the browser first sends a short request of its own asking the server whether the real one is allowed. It is called a preflight, and it shows up in the Network panel as an extra row of its own, with preflight as its type.
When the Server Says Nothing
The site example.com, a name reserved for examples, sends no allow-origin header. So a page on another origin can send it a request, but not read the answer.
async function tryOther() { try { const response = await fetch("https://example.com/"); console.log(response.status); } catch (error) { console.log("Caught:", error.name); } } tryOther();
Access to fetch at 'https://example.com/' from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. Caught: TypeError
Run it in the Console of any page on your preview server. The Console shows a long red message from the browser: access to the fetch was blocked by CORS policy, because no allow-origin header was present. The origin it names is your preview server's address. A second, shorter red line may follow: the browser's note that the same request failed. Then your own line appears: Caught, TypeError. The request went out and example.com answered, but the browser kept the answer from your script, so to your code it looks exactly like a failed fetch. Open https://example.com/ in a new tab and it loads normally. That is you visiting the site, not a script on another site reading it.
Notice what the script got: only a plain TypeError, with no reason attached. That is on purpose. If scripts could learn why a cross-origin request failed, they could use the details to probe other sites. The full explanation is for you, the person at the keyboard, in the Console. The script gets nothing it could misuse.
Advice found online often suggests setting the request's mode to no-cors. That does not fix anything for you: the request is then allowed to go through, but the answer is sealed, and your script still cannot read a word of it.
What Can You Fix, and What Can't You?
The permission lives on the server. Nothing a page writes can grant it access, because the whole point is that the page does not get to decide. That leaves two real options. Use an API that allows pages to read it; public APIs usually say so in their documentation. Or have a server of your own ask on the page's behalf: the same-origin policy is a rule of browsers, so a server fetching from another server is not blocked at all. That server is backend work, and Backend Deep Dive builds one.
That same fact explains who CORS protects. A program that is not a browser, such as curl, can call any server directly and read the answer. So CORS does nothing to protect a server from being called. It protects the visitor, whose browser, cookies and signed-in sessions would otherwise be available to every page they open.
- "CORS is a bug in my code." It is the browser enforcing another server's rules. Your code may be perfectly correct.
- "A browser extension that disables CORS fixes it." It changes your own browser only. Every visitor to your site still gets the error.
- "CORS protects the server." It protects the visitor. Anything that is not a browser can still call the server directly.
- You will meet this error on your first API project, and now you know what it means and where the fix lives.
- It is one of the security boundaries that keep the web usable, and it explains a whole class of confusing errors.
Knowledge Check
A page is open at http://localhost:3000/index.html. Which address has the same origin?
https://localhost:3000/index.htmlhttp://localhost:5500/index.htmlhttp://localhost:3000/data/projects.jsonhttp://127.0.0.1:3000/index.html
A fetch to another site's API is blocked by CORS. Where does the fix belong?
- In your page, by adding a header to the request
- On that server, which must send an allow header
- In your fetch call, by setting the mode to no-cors
- In your own browser, by installing an extension
Who does the same-origin policy mainly protect?
- The visitor, whose signed-in sites stay unread
- The server, which can no longer be called at all
- The page's author, whose code cannot be copied
- The network, which now carries fewer requests
Why does the script receive only a plain TypeError when a request is blocked?
- Because the request was never sent in the first place
- Because CORS errors happen before the page has loaded
- So that the Console stays free of extra messages
- So that pages cannot learn details about other sites
You got correct