cors-anywhere alternative: migrate to corsproxy.dev
If your frontend depends on cors-anywhere.herokuapp.com and requests have started failing, corsproxy.dev is a hosted alternative with a nearly identical request shape. It uses an account-level daily quota (500 requests/day on Free) instead of a shared demo instance.
The replacement
// Before: cors-anywhere demo
fetch('https://cors-anywhere.herokuapp.com/https://api.example.com/users')
// After: corsproxy.dev
fetch('https://api.corsproxy.dev/proxy?key=YOUR_KEY&url=' +
encodeURIComponent('https://api.example.com/users'))
Current limits
- Free: 500 requests and 250 MB per day per account, no credit card.
- Pro: $5/month for 20,000 requests and 10 GB per day, plus edge caching.
- Quotas reset at 00:00 UTC. GET and POST only.
Get a free key, swap the URL, and you're done. The rest of this page covers wrappers, axios, and what behaves differently.
Why cors-anywhere stopped working for you
The public cors-anywhere.herokuapp.com instance is a demo of the open-source Rob--W/cors-anywhere project, not a production service. The project's README says access to the demo is limited and describes the current rules, so code that called it freely can start failing without any change on your side.
That's a reasonable call by the maintainer: an open proxy on the public internet attracts abuse. It still leaves a lot of frontends quietly broken.
"403 Forbidden" from cors-anywhere.herokuapp.com
A 403 from the demo, with a body that says See /corsdemo for more info, means you haven't opted in. The /corsdemo page says the demo is for development only and has a button that temporarily unlocks it.
That unlock applies to your browser only. Your users' browsers still get the 403, so it can help you test locally but can't fix a deployed site. For anything other people use, migrate to a proxy you control or a hosted one.
The migration in one diff
The request shape is nearly identical. You change the host, move the target URL from the path into a url query parameter, and add your API key:
// Before: cors-anywhere demo
fetch('https://cors-anywhere.herokuapp.com/' +
'https://api.example.com/users')
// After: corsproxy.dev
fetch('https://api.corsproxy.dev/proxy?key=YOUR_KEY&url=' +
encodeURIComponent('https://api.example.com/users'))
Two differences worth flagging:
- The target URL is a query parameter, not a path suffix. URL-encode it with
encodeURIComponentso the target's own query string isn't mistaken for the proxy's parameters. - You send an API key. In the browser it goes in
?key=; from a server, use theX-API-Keyheader. The Free tier is 500 requests/day per account, no credit card. Sign in with your email at app.corsproxy.dev and generate a key from the dashboard.
Common patterns and their replacements
1. Wrapper function in your codebase
If you abstracted cors-anywhere into a helper, the replacement is a few lines:
// Before
const corsFetch = (url, opts) =>
fetch('https://cors-anywhere.herokuapp.com/' + url, opts);
// After
const PROXY = 'https://api.corsproxy.dev/proxy';
const KEY = import.meta.env.VITE_PROXY_KEY;
const corsFetch = (url, opts) =>
fetch(`${PROXY}?key=${encodeURIComponent(KEY)}&url=${encodeURIComponent(url)}`, opts);
The managed proxy accepts GET and POST only, so a wrapper that sends PUT, PATCH or DELETE needs a different route for those calls.
2. Axios baseURL
// Before
const api = axios.create({
baseURL: 'https://cors-anywhere.herokuapp.com/https://api.example.com'
});
// After: let axios encode the query parameters
const api = axios.create({ baseURL: 'https://api.corsproxy.dev/proxy' });
const KEY = import.meta.env.VITE_PROXY_KEY;
const proxied = (path) =>
api.get('', { params: { key: KEY, url: 'https://api.example.com' + path } });
proxied('/users');
3. Background fetcher / server-side script
If the requests are coming from your backend (a cron job, a Lambda, anything not in a browser), you probably don't need a CORS proxy at all, because CORS is a browser-only restriction. Drop the proxy entirely and call the upstream directly.
What you get that the cors-anywhere demo doesn't have
- A service meant for real use. The hosted API isn't a demo instance; status is published at corsproxy.dev/status.
- An exact quota. A daily request quota per account, enforced atomically and reset at 00:00 UTC, with
X-RateLimit-*headers so your code can see how much is left. - Usage and logs in the dashboard. Request logs, including the URLs each key requested, are kept for 30 days.
- Server-side secrets. Managed upstream headers let you attach an upstream API key without shipping it to the browser.
- Edge caching on Pro. Add
ttl=<seconds>to cache 200 GET responses at Cloudflare's edge.
What changes vs running your own cors-anywhere
If you self-hosted cors-anywhere rather than using the demo, you configured it yourself, and corsproxy.dev may behave differently. It accepts GET and POST only, strips the browser's Cookie and Authorization headers before the upstream call, strips Set-Cookie from responses, and returns upstream redirects instead of following them. The reasoning is in our security writeup.
If you need full control, we also publish a separate, simpler open-source CORS relay in Go you can self-host. It is not the code behind corsproxy.dev: it uses /?url=, forwards all headers including cookies, and has no keys. See self-host vs managed.
Quick migration
Get a free key, swap the host and move the target into ?url=, and you're back to working CORS proxying.