Infra Guide for AI Tool Builders - Part 4: CORS in Simple Words: What It Is and How to Fix It

Published: February 18, 2026

CORS - Cross-Origin Resource Sharing

If you are building tools, AI or otherwise, you would have run into CORS issues. If not, you will.

Say you want to download a file from Google Drive into your browser app or just pull Yahoo Finance data using yfinance (needs Pyodide).

What is CORS?

When you download from a terminal (like using curl or python) - this always works. No restrictions.

But inside a browser (JavaScript code running in a web app) - there are restrictions. When your browser code tries to fetch data from another website (like Google Drive), that server must explicitly say "Yes, browsers are allowed to access this." They do this by including a special header in the response: Access-Control-Allow-Origin: *

This is CORS - Cross-Origin Resource Sharing. It's a browser security feature.

What's the problem?

Many services (e.g. Yahoo Finance, GitHub Releases, Google Drive, Dropbox etc) don't include this header. So your browser blocks your JavaScript from reading the response. It says: "Sorry, this server didn't give me permission to share this data with a browser."

Wait - does the data actually reach the browser?

Yes. This is the confusing part. The browser makes the request, the server sends the data back. The data actually arrives at your browser. But before handing it to your JavaScript code, the browser checks for that Access-Control-Allow-Origin header. If it's missing - your JavaScript can't touch the data. The browser's security layer sits between the network and your code.

Nuance: Sometimes the data doesn't even arrive - Preflight Requests

Everything above applies to simple requests - basic GET requests that just read data. But for more complex requests - POST with JSON body, requests with custom headers like Authorization, PUT, DELETE - the browser does something different.

What is a preflight request? Before sending the actual request, the browser sends a lightweight OPTIONS request first. It asks the server: "Would you accept this kind of request from a browser?" If the server says yes (returns the right CORS headers), the browser sends the real request. If not, it stops right there. The real request never goes out. The data never arrives.

Why does the browser send a preflight? It's a safety check. A POST or DELETE can change data on the server - create records, delete data, trigger actions. The browser's logic: "Before I send something that could modify data on a server that might not be expecting browser requests, let me check first." This protects older APIs that were never designed to be called from random websites.

Can I disable it? No. The browser decides automatically. You cannot tell the browser "skip the preflight." It's not a setting or a flag in your code.

How does the browser decide when to send a preflight? Simple rules. The browser skips preflight (sends directly) if ALL of these are true: method is GET, HEAD, or POST; only standard headers (no Authorization, no custom headers); and if POST, content type is only form data or plain text. The moment you break any of these - say you add Content-Type: application/json or an Authorization header - the preflight happens automatically. Most modern API calls use JSON and auth headers, so most calls trigger preflight.

Does it slow things down? It's an extra round-trip but it's fast (just headers, no body) - you generally don't notice it.

The fix is the same either way - use a proxy. But now you know why sometimes you see that OPTIONS request in your DevTools Network tab before the actual call.

So where is the data sitting?

It's in the browser's network layer. The bytes came in, the HTTP response was parsed. But it's walled off from your JavaScript. Think of it like a customs officer who received your package but won't hand it to you because the paperwork (CORS headers) is missing.

Can I see it in DevTools?

Yes! This trips people up. Go to DevTools > Network tab, click the request - you can see the full response body with the actual data right there. You can read it. The browser has it.

But your fetch() call still throws a CORS error. DevTools has elevated privileges that bypass the same-origin policy. So you're staring at the data in DevTools while your code can't access it. Frustrating.

What's the solution?

You add a middleman - also called a Proxy. Instead of fetching directly from the offending API, your browser fetches from a proxy. The proxy fetches the data server-side (which always works - CORS is a browser-only restriction), then sends it to your browser with the proper CORS headers added. Your browser is happy, and you get your data.

CORS issues are fairly common when building browser-based apps. Knowing how to use a proxy to bypass CORS is an essential skill if you're building deployable apps and data tools.

Trap: Don't use mode: 'no-cors' - it doesn't fix anything

When you hit a CORS error, you'll find suggestions online to add mode: 'no-cors' to your fetch call. It looks like a fix. The error goes away. But your data is gone too.

What no-cors actually does: it tells the browser "I know this will fail CORS, send it anyway but I accept that I can't read the response." The browser makes the request, gets the response, and gives you an empty opaque response. No data. No error either. Just nothing.

It's useful in very specific cases (like sending analytics pings where you don't care about the response), but for fetching data - it's a trap. The CORS error disappears but so does your data. Use a proxy instead.

How I handle it - and which one to pick when

Numerous ways to set up a proxy. I typically end up using one of these 3. Here's the order I think about them.

1. Cloudflare Workers - The Pure Proxy (Free. No Domain Required)

This is the simplest option for pure pass-through. Your browser calls the Cloudflare Worker, the Worker fetches from the actual API, adds CORS headers, sends it back. That's it. No domain needed - you get a free workers.dev subdomain. No server to manage. Free Cloudflare account is enough. You can also add authentication if needed.

Great for: any situation where you just need to pass data through without processing it. If the API you're calling works fine but just doesn't have CORS headers - this is the quickest fix. Check the CORS section in xlwings Lite Data Importer - there's a complete text guide there. Copy the guide, paste to your AI coder, AI sets it up for you.

2. Vercel Serverless Functions - The Mini Backend (Free Tier)

This is a very nifty feature. If you have a React app on Vercel, you can create an api/ folder in your project. Any file inside api/ becomes a serverless function - essentially a mini backend endpoint. It runs on Vercel's servers, not in the browser. So no CORS issues because the request goes from Vercel's server (server-to-server), not from the browser.

It's like getting a mini FastAPI backend for free - except in JavaScript. You can do fair amount of processing there - not just proxy calls. Anything that can be done in JavaScript, you can do there. Data transformation, API orchestration, auth token handling.

The free tier gives you up to 12 serverless functions - more than enough for most apps. If you run into the limit, you can always combine functions. Execution timeout on free tier is currently up to 300 seconds (5 minutes) - so even long-running queries or heavier processing work fine.

For local development, you need the Vercel CLI (vercel dev) to run serverless functions locally - they won't work with just npm start since they need Vercel's runtime.

In my newer React apps, I've moved all API calls from the frontend to serverless functions. Everything goes through the server side. This adds an extra layer of security (API keys never exposed in browser code) and CORS issues are automatically taken care of. That's my default approach now.

Example: My IMDb Movie Explorer app. For source code hit 'Docs' on the app site (Press F9 if you don't see top menu).

3. FastAPI Backend - For Heavy Python Processing (Not Free)

This is not required for all cases. For simple pass-through requests, this is overkill - Cloudflare Workers or Vercel serverless handle that easily.

Where this makes sense: when you need serious Python processing that can't be done in JavaScript. Libraries like yfinance, pandas, numpy, scikit-learn - things that only exist in the Python ecosystem. Or when you already have a Python backend doing other things and you just want to add another endpoint.

Example: Yahoo Finance Data Extractor. A FastAPI backend that runs yfinance. Public and open. Pull Yahoo Finance data from any browser.

So which one do I pick?

Typically: if I'm building a React app, Vercel serverless functions are my first choice by default - everything goes through the server side. If something can't be done there (needs Python), I use my FastAPI backend. For standalone pass-through where I just need to proxy an API call quickly, Cloudflare Workers - doesn't even need a domain.

localhost:3000 calling localhost:8000 is also cross-origin

This catches people during development. Your React app runs on localhost:3000. Your backend runs on localhost:8000. Same machine. Same "localhost." But different ports. To the browser, these are different origins. So yes - you get CORS errors even when both are running on your own laptop.

That's why your FastAPI backend needs CORS middleware even during local development. In FastAPI you add CORSMiddleware with allow_origins=["*"] (or specific origins for production). Without it, your local frontend can't talk to your local backend. Same machine, different ports, different origins.

Other Options (haven't used these - worth knowing about)

There are other solutions people use. Public CORS proxy services like allorigins.win and corsproxy.io let you just prepend their URL to your target URL - quick for testing but you're routing through someone else's server with no guarantees on uptime or rate limits. Not suitable for production. CORS Anywhere is an open source Node.js proxy you can self-host - popular in tutorials, basically what Cloudflare Workers does but you manage the server. Browser extensions like "CORS Unblock" disable CORS checking entirely - fine for local dev, useless for production since your users won't have it installed. Nginx/Caddy reverse proxy can also forward requests and add CORS headers - but if you already have a FastAPI backend, this is redundant. Your backend already handles it.

Best Way to Use This

Just copy paste this post to your AI Coder. It will explain the differences and trade-offs and applicability for your specific apps. It can clone the repos and guides if you ask it to - but not really required. This is straightforward stuff for them. They know it blind.

Infra Guide Series