CORS Handbook
The complete guide to understanding and fixing CORS errors.
Built by headertest.com
I’ve seen a lot of teams blame Heroku when their frontend suddenly starts throwing CORS errors after deployment. Usually, Heroku is not the problem. Heroku just makes bad CORS assumptions painfully visible. This case study comes from a very common setup: React frontend on one Heroku app or a custom domain Node/Express API on another Heroku app Everything works locally Production blows up with No 'Access-Control-Allow-Origin' header is present The painful part is that the app often looks fine in Postman, curl, or server-to-server tests. Then the browser blocks it anyway. ...
Chrome platform apps have always been a weird corner of the web platform. They look like web apps, use web APIs, and make HTTP requests like a browser. But they also run with elevated privileges and their network behavior does not match a normal tab. If you work on CORS-heavy APIs, that difference matters. This guide is the practical version: what changes, what still applies, and what headers you actually need. ...
Real-time apps make CORS weirder than plain old fetch(). A normal API request is easy to reason about: browser sends an Origin, server returns Access-Control-Allow-Origin, done. Real-time stacks like Socket.IO and SignalR add negotiation endpoints, long polling fallbacks, credentials, sticky sessions, and WebSocket upgrades. That combination creates the kind of bug where everything works locally, then production starts throwing “CORS policy blocked” while your websocket dashboard looks perfectly healthy. I’ve hit this enough times that I now treat real-time CORS as a separate problem, not just “API CORS but more.” ...
Headless CMS preview sounds simple until the browser gets involved. Your editor clicks “Preview draft”, your frontend tries to fetch unpublished content from a CMS API on another origin, and suddenly the browser throws a CORS error that says almost nothing useful. I’ve seen teams lose hours here because they treated preview like normal production API traffic. It isn’t. Preview usually combines the hardest parts of cross-origin browser security in one flow: ...
Cloudflare Access is great at putting identity in front of internal apps and APIs. CORS is great at making frontend apps talk to APIs across origins. Put them together and you get a setup that works well — until it really doesn’t. I’ve seen teams assume Cloudflare Access will “just handle” cross-origin browser requests. It won’t. Access solves authentication and authorization at the edge. CORS is still your job, and the browser is still brutally strict about it. ...
Shopify webhooks and CORS get mixed up constantly. I’ve seen teams burn hours “fixing CORS” on webhook endpoints that were never touched by a browser in the first place. Shopify sends webhooks server-to-server. Browsers enforce CORS. Those are different worlds. So the short version is: Shopify webhook delivery does not require CORS Your frontend talking to your backend may require CORS Your webhook endpoint should usually not be exposed for browser cross-origin access at all That distinction saves a lot of confusion. ...
A lot of teams treat CORS like a checkbox: add Access-Control-Allow-Origin, ship it, move on. That usually works right up until the frontend needs one custom header, auth cookies enter the picture, or someone decides * is fine everywhere. I’ve seen this go wrong in a very normal setup: a React frontend on app.example.com, an API on api.example.com, and a CDN in front of both. Nothing exotic. The bug report sounded simple: ...
CORS on Railway usually breaks for the same boring reasons it breaks everywhere else: wrong origin, missing preflight handling, credentials mixed with *, or a proxy layer eating headers. Railway itself is not the hard part. Your app is. This guide is the version I wish I could paste into every “CORS error on Railway” thread. What Railway changes Railway gives you deployed services on Railway-owned domains and often custom domains on top. That means your frontend and backend commonly end up on different origins: ...
Stripe webhooks and CORS get mixed together constantly, and that usually means someone is solving the wrong problem. I’ve seen this play out the same way more than once: payments work in Stripe Checkout, the webhook endpoint is live, then somebody opens DevTools, sees a failed browser request, and starts adding Access-Control-Allow-Origin: * to the webhook route. A few commits later, webhook signature verification breaks, preflight requests start showing up where they never mattered, and the team is less sure than before what CORS even applies to. ...
Caddy makes the easy path easy, but CORS is still CORS. The browser enforces it, the server has to answer correctly, and one wrong header can turn a simple API call into a weird frontend bug that eats an afternoon. This guide is the version I wish I had the first few times I configured CORS behind a reverse proxy. What CORS is actually doing CORS is the browser asking: ...