Best used with an AI agent

40+ live apps, open data APIs, MCP servers, and 200+ guides - more than anyone wants to click through. Point your AI here and it reads the whole map and does the work: finds the tool, pulls the data, runs the analysis, and hands you the links.

Here for the open-source code? Your agent finds the right repo for you - and can even clone and deploy it.

Prefer to explore on your own? Go right ahead.

Paste this to Claude Code, Codex, or any AI agent:
Go to tigzig.com and read tigzig.com/llms.txt. It is a practitioner toolkit - 40+ analytics apps, open no-auth data APIs, MCP servers, open-source repos (github.com/amararun), and 200+ build guides. Help me [your task]. Surface the exact links; where there is an API or MCP, call it directly; and if I want to self-host, find the repo and help me deploy it.

CORS Anywhere Alternative: A Free Cloudflare Worker CORS Proxy (POST and Any URL)

Published: June 22, 2026

Infra Guide for AI Tool Builders

A lot of people look for a cors-anywhere alternative or replacement they can run themselves. Something general: give it any URL, any method, including a POST with a JSON body, and it adds the CORS headers and passes the call through. The well known ones are the Node project cors-anywhere and its Cloudflare port cloudflare-cors-anywhere.

This post gives you that, written my way: a small, free Cloudflare Worker that works as a general CORS proxy. You run your own copy. No server, no custom domain, free Cloudflare account is enough (100k requests per day). The full code is below, and it is also in a GitHub repo you can clone or hand to your AI coder.

If you have not read the main CORS post yet, start there: Free CORS Proxy for Yahoo Finance and Any API. It explains what CORS is in plain words, the preflight nuance, the no-cors trap, and when to use a Worker vs a Vercel serverless function vs a FastAPI backend. This post assumes you already know why you need a proxy and just want the general purpose one.

How this is different from the file proxy

The Worker in the main post is on purpose a narrow one. It was built to pull data files into a browser app (GitHub, Google Drive, Dropbox, and Yahoo Finance if you add it). So it only does GET, and it only fetches from a small allow list of file hosts.

This one is the general version. The difference is scope:

Same idea, bigger job. If you just need to pull files into the browser, the file proxy is simpler and safer. If you need to proxy a real API call, including POST, use the one here.

How you call it

Once it is deployed, put your target URL, URL-encoded, in the url query parameter:

https://your-worker.workers.dev/?url=<encoded-target-url>

The older ?uri= spelling also works, so forks that expect ?uri= will not break.

A GET call:

https://your-worker.workers.dev/?url=https://query1.finance.yahoo.com/v8/finance/chart/AAPL

A POST call from browser JavaScript, with a JSON body:

fetch("https://your-worker.workers.dev/?url=" + encodeURIComponent("https://api.example.com/data"), {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ hello: "world" }),
});

The Worker reads the body, sends it on to the target, gets the response, adds the CORS headers, and streams it back to your browser.

Locking it down (read this before you rely on it)

Here is the thing to understand first. A CORS proxy is called from the browser. So anything the browser has to send to the proxy, like a key, is visible to anyone who opens the network tab on your page. You cannot truly keep a secret in a browser-called proxy. Everything below is about raising the bar and capping the damage, not making it airtight.

Out of the box the code below is fully open, so it just works while you test. Turn the locks on before you put it into real use. There are four.

1. Origin allow list

"Origin" is the website the request comes from. It is a domain, like https://yourapp.com. It is not anyone's laptop IP. When browser JavaScript makes a cross-site call, the browser automatically attaches an Origin header, and page JavaScript cannot change or fake it. So if you list your own domains, only pages served from those domains can use your proxy from a browser. The Worker checks this and rejects others before it fetches anything, so a blocked call costs you nothing.

The honest caveat: this only stops other websites. A script or another server is not a browser, so it can send any Origin it likes. So origin lock stops a random site from piggybacking on your proxy in their browser app, which is the main abuse. It does not stop a determined script.

2. Target allow list

This is which destination hosts the proxy is allowed to fetch. Narrowing this is the strongest single control. If your app only ever talks to one API, list just that host. Even if someone gets past everything else, they can only reach the hosts you allow, so your proxy is useless as a general tool for them.

3. API key (two ways)

If you set a Worker secret named API_KEY, every caller must send a matching X-API-Key header. There are two clean ways to use it:

Because a browser-called proxy cannot truly hide a secret, the API key mostly helps for script callers and raises the bar for casual abuse. For browser use, the origin allow list is the natural lock.

4. Rate limit, and the free-tier safety net

You can add Cloudflare's rate-limit binding (a few lines in wrangler.toml, shown below) to cap how many calls one IP can make. On top of that, the free Cloudflare plan caps you at 100k requests per day. If an open proxy ever gets hammered, the worst case is that it stops working for the day once the cap is hit. It fails closed. You do not get a surprise bill, which is the opposite of leaving something open on a pay-per-use cloud.

One more safety point: Cloudflare Workers cannot fetch private or localhost addresses, so this proxy can only reach the public web. It cannot be pointed at your internal network.

Deploy it

Two ways.

Dashboard, no tools needed:

  1. Go to dash.cloudflare.com and open Workers and Pages, then Create, then Create Worker.
  2. Give it a name, click Deploy, then Edit code.
  3. Delete the default code, paste in the Worker code below, click Save and Deploy.
  4. Your URL shows at the top: https://your-name.workers.dev.

CLI, or let your AI coder do it:

CLOUDFLARE_API_TOKEN=your_token npx wrangler deploy

You need one Cloudflare API token with "Edit Cloudflare Workers" permission. Create it at dash.cloudflare.com/profile/api-tokens using the "Edit Cloudflare Workers" template.

To turn on the API key lock:

npx wrangler secret put API_KEY

The Worker code

Save as cors-proxy-worker.js. To lock it down, edit ALLOWED_ORIGINS and ALLOWED_TARGETS near the top (the comments tell you how). Left as is, it is fully open.

/**
 * Generic CORS Proxy - Cloudflare Worker (a cors-anywhere style proxy)
 *
 * Your browser calls this Worker, the Worker fetches the target URL on the
 * server side (where CORS does not apply), adds CORS headers, and sends the
 * result back. It passes through any method (GET, POST, PUT, PATCH, DELETE) and
 * forwards the request body and headers.
 *
 * Call it:  https://your-worker.workers.dev/?url=<encoded-target-url>
 * The older "?uri=" spelling also works.
 */

// 1. Which websites may call this proxy. Use ["*"] to allow any site.
//    To lock it to your own apps: ["https://yourapp.com", "https://www.yourapp.com"]
const ALLOWED_ORIGINS = ["*"];

// 2. Which destination hosts this proxy may fetch. Use ["*"] for any URL.
//    To lock it down: ["api.example.com", "query1.finance.yahoo.com"]
const ALLOWED_TARGETS = ["*"];

// Methods passed through to the target.
const ALLOWED_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"];

function originAllowed(origin) {
  if (ALLOWED_ORIGINS.includes("*")) return true;
  if (!origin) return false;
  return ALLOWED_ORIGINS.includes(origin);
}

function targetAllowed(targetUrl) {
  if (ALLOWED_TARGETS.includes("*")) return true;
  try {
    const host = new URL(targetUrl).hostname.toLowerCase();
    return ALLOWED_TARGETS.some(d => host === d || host.endsWith("." + d));
  } catch {
    return false;
  }
}

function corsHeaders(origin) {
  const open = ALLOWED_ORIGINS.includes("*");
  const headers = {
    "Access-Control-Allow-Origin": open ? "*" : (origin || ""),
    "Access-Control-Allow-Methods": ALLOWED_METHODS.join(", "),
    "Access-Control-Allow-Headers": "Content-Type, Authorization, X-API-Key, Accept",
    "Access-Control-Max-Age": "86400",
  };
  if (!open) headers["Vary"] = "Origin";
  return headers;
}

function jsonResponse(obj, status, origin) {
  return new Response(JSON.stringify(obj, null, 2), {
    status,
    headers: { "Content-Type": "application/json", ...corsHeaders(origin) },
  });
}

export default {
  async fetch(request, env) {
    const origin = request.headers.get("Origin");

    // Answer the browser preflight first.
    if (request.method === "OPTIONS") {
      return new Response(null, { headers: corsHeaders(origin) });
    }

    // Lock 1: origin allow list. Reject early so disallowed sites cost nothing.
    if (!originAllowed(origin)) {
      return jsonResponse({ error: "Origin not allowed" }, 403, origin);
    }

    // Lock 3: optional API key (set a Worker secret named API_KEY to turn on).
    if (env && env.API_KEY) {
      if (request.headers.get("X-API-Key") !== env.API_KEY) {
        return jsonResponse({ error: "Missing or invalid X-API-Key" }, 401, origin);
      }
    }

    // Lock 4: optional rate limit (only runs if the binding is configured).
    if (env && env.RATE_LIMITER) {
      const ip = request.headers.get("CF-Connecting-IP") || "anon";
      const { success } = await env.RATE_LIMITER.limit({ key: ip });
      if (!success) return jsonResponse({ error: "Rate limit exceeded" }, 429, origin);
    }

    // Read the target URL from ?url= (or ?uri=).
    const reqUrl = new URL(request.url);
    const targetUrl = reqUrl.searchParams.get("url") || reqUrl.searchParams.get("uri");
    if (!targetUrl) {
      return jsonResponse({
        name: "Generic CORS Proxy",
        usage: `${request.method} ${reqUrl.origin}/?url=<encoded-target-url>`,
        note: "Put the target URL, URL-encoded, in the url (or uri) query parameter.",
      }, 400, origin);
    }

    // Lock 2: target allow list.
    if (!targetAllowed(targetUrl)) {
      return jsonResponse({ error: "Target host not allowed" }, 403, origin);
    }

    // Build the upstream request. Copy method, body, and most headers, but drop
    // headers that should not be forwarded (our own X-API-Key, and the
    // Cloudflare / origin headers the platform adds).
    const upstreamHeaders = new Headers(request.headers);
    ["host", "origin", "referer", "x-api-key",
     "cf-connecting-ip", "cf-ipcountry", "cf-ray", "cf-visitor", "x-forwarded-proto"]
      .forEach(h => upstreamHeaders.delete(h));

    const hasBody = !["GET", "HEAD"].includes(request.method);

    let upstream;
    try {
      upstream = await fetch(targetUrl, {
        method: request.method,
        headers: upstreamHeaders,
        body: hasBody ? request.body : undefined,
        redirect: "follow",
      });
    } catch (err) {
      return jsonResponse({ error: "Upstream fetch failed", message: err.message }, 502, origin);
    }

    // Send the response back, adding our CORS headers and removing headers that
    // would block the browser from reading or embedding it.
    const respHeaders = new Headers(upstream.headers);
    const ch = corsHeaders(origin);
    Object.keys(ch).forEach(k => respHeaders.set(k, ch[k]));
    respHeaders.set("Access-Control-Expose-Headers", "*");
    respHeaders.delete("content-security-policy");
    respHeaders.delete("x-frame-options");

    return new Response(upstream.body, {
      status: upstream.status,
      statusText: upstream.statusText,
      headers: respHeaders,
    });
  },
};

And the config, save as wrangler.toml in the same folder. The rate limit block is commented out, so it is optional. The Worker only runs the rate check if the binding is present, so it is safe to leave off.

name = "cors-proxy"
main = "cors-proxy-worker.js"
compatibility_date = "2024-09-01"

# Optional rate limit. Uncomment to turn it on. limit = requests, period = 10 or 60 seconds.
# [[unsafe.bindings]]
# name = "RATE_LIMITER"
# type = "ratelimit"
# namespace_id = "1001"
# simple = { limit = 100, period = 60 }
[observability.logs]
enabled = true

GitHub repo

The same code, ready to clone or fork: github.com/amararun/cloudflare-cors-proxy. It has the Worker, the wrangler.toml, and a short README.

The real shortcut

Here is the honest bit. If you are already working with an AI coder like Claude or Cursor, you do not need to copy any of this. Point it at this page or at the GitHub repo and say "deploy this CORS proxy for me, lock it to my domain." It will create the Worker, set the token, and give you back a live URL in a couple of minutes. The code above is here so you can read it and so the agent has something exact to work from.

That is the wider point of this site. Stop hunting through search results for snippets. Ask your agent, point it at a known good source, and let it do the wiring. There are 30+ open tools, APIs, and guides here built around that idea.