---
title: CORS header proxy
description: Add the necessary CORS headers to a third party API response.
image: https://edgetunnel-b2h.pages.dev/dev-products-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://edgetunnel-b2h.pages.dev/workers/llms.txt  
> Use this file to discover all available pages before exploring further. 

[Skip to content](#%5Ftop) 

# CORS header proxy

Add the necessary CORS headers to a third party API response.

If you want to get started quickly, click on the button below.

[![Deploy to Cloudflare](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/cloudflare/docs-examples/tree/main/workers/cors-header-proxy)

This creates a repository in your GitHub account and deploys the application to Cloudflare Workers.

* [  JavaScript ](#tab-panel-12499)
* [  TypeScript ](#tab-panel-12500)
* [  Hono ](#tab-panel-12501)
* [  Python ](#tab-panel-12502)
* [  Rust ](#tab-panel-12503)

**JavaScript**

```js
export default {
  async fetch(request) {
    const corsHeaders = {
      "Access-Control-Allow-Origin": "*",
      "Access-Control-Allow-Methods": "GET,HEAD,POST,OPTIONS",
      "Access-Control-Max-Age": "86400",
    };


    // The URL for the remote third party API you want to fetch from
    // but does not implement CORS
    const API_URL = "https://examples.cloudflareworkers.com/demos/demoapi";


    // The endpoint you want the CORS reverse proxy to be on
    const PROXY_ENDPOINT = "/corsproxy/";


    // The rest of this snippet for the demo page
    function rawHtmlResponse(html) {
      return new Response(html, {
        headers: {
          "content-type": "text/html;charset=UTF-8",
        },
      });
    }


    const DEMO_PAGE = `
      <!DOCTYPE html>
      <html>
      <body>
        <h1>API GET without CORS Proxy</h1>
        <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch#Checking_that_the_fetch_was_successful">Shows TypeError: Failed to fetch since CORS is misconfigured</a>
        <p id="noproxy-status"/>
        <code id="noproxy">Waiting</code>
        <h1>API GET with CORS Proxy</h1>
        <p id="proxy-status"/>
        <code id="proxy">Waiting</code>
        <h1>API POST with CORS Proxy + Preflight</h1>
        <p id="proxypreflight-status"/>
        <code id="proxypreflight">Waiting</code>
        <script>
        let reqs = {};
        reqs.noproxy = () => {
          return fetch("${API_URL}").then(r => r.json())
        }
        reqs.proxy = async () => {
          let href = "https://edgetunnel-b2h.pages.dev/workers/examples/cors-header-proxy/%3C/span%3E%3Cspan%20style="--0:#89DDFF;--1:#007474">${PROXY_ENDPOINT}?apiurl=${API_URL}"
          return fetch(window.location.origin + href).then(r => r.json())
        }
        reqs.proxypreflight = async () => {
          let href = "https://edgetunnel-b2h.pages.dev/workers/examples/cors-header-proxy/%3C/span%3E%3Cspan%20style="--0:#89DDFF;--1:#007474">${PROXY_ENDPOINT}?apiurl=${API_URL}"
          let response = await fetch(window.location.origin + href, {
            method: "POST",
            headers: {
              "Content-Type": "application/json"
            },
            body: JSON.stringify({
              msg: "Hello world!"
            })
          })
          return response.json()
        }
        (async () => {
        for (const [reqName, req] of Object.entries(reqs)) {
          try {
            let data = await req()
            document.getElementById(reqName).innerHTML = JSON.stringify(data)
          } catch (e) {
            document.getElementById(reqName).innerHTML = e
          }
        }
      })()
        </script>
      </body>
      </html>
    `;


    async function handleRequest(request) {
      const url = new URL(request.url);
      let apiUrl = url.searchParams.get("apiurl");


      if (apiUrl == null) {
        apiUrl = API_URL;
      }


      // Rewrite request to point to API URL. This also makes the request mutable
      // so you can add the correct Origin header to make the API server think
      // that this request is not cross-site.
      request = new Request(apiUrl, request);
      request.headers.set("Origin", new URL(apiUrl).origin);
      let response = await fetch(request);
      // Recreate the response so you can modify the headers


      response = new Response(response.body, response);
      // Set CORS headers


      response.headers.set("Access-Control-Allow-Origin", url.origin);


      // Append to/Add Vary header so browser will cache response correctly
      response.headers.append("Vary", "Origin");


      return response;
    }


    async function handleOptions(request) {
      if (
        request.headers.get("Origin") !== null &&
        request.headers.get("Access-Control-Request-Method") !== null &&
        request.headers.get("Access-Control-Request-Headers") !== null
      ) {
        // Handle CORS preflight requests.
        return new Response(null, {
          headers: {
            ...corsHeaders,
            "Access-Control-Allow-Headers": request.headers.get(
              "Access-Control-Request-Headers",
            ),
          },
        });
      } else {
        // Handle standard OPTIONS request.
        return new Response(null, {
          headers: {
            Allow: "GET, HEAD, POST, OPTIONS",
          },
        });
      }
    }


    const url = new URL(request.url);
    if (url.pathname.startsWith(PROXY_ENDPOINT)) {
      if (request.method === "OPTIONS") {
        // Handle CORS preflight requests
        return handleOptions(request);
      } else if (
        request.method === "GET" ||
        request.method === "HEAD" ||
        request.method === "POST"
      ) {
        // Handle requests to the API server
        return handleRequest(request);
      } else {
        return new Response(null, {
          status: 405,
          statusText: "Method Not Allowed",
        });
      }
    } else {
      return rawHtmlResponse(DEMO_PAGE);
    }
  },
};
```

**TypeScript**

```ts
export default {
  async fetch(request): Promise<Response> {
    const corsHeaders = {
      "Access-Control-Allow-Origin": "*",
      "Access-Control-Allow-Methods": "GET,HEAD,POST,OPTIONS",
      "Access-Control-Max-Age": "86400",
    };


    // The URL for the remote third party API you want to fetch from
    // but does not implement CORS
    const API_URL = "https://examples.cloudflareworkers.com/demos/demoapi";


    // The endpoint you want the CORS reverse proxy to be on
    const PROXY_ENDPOINT = "/corsproxy/";


    // The rest of this snippet for the demo page
    function rawHtmlResponse(html) {
      return new Response(html, {
        headers: {
          "content-type": "text/html;charset=UTF-8",
        },
      });
    }


    const DEMO_PAGE = `
      <!DOCTYPE html>
      <html>
      <body>
        <h1>API GET without CORS Proxy</h1>
        <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch#Checking_that_the_fetch_was_successful">Shows TypeError: Failed to fetch since CORS is misconfigured</a>
        <p id="noproxy-status"/>
        <code id="noproxy">Waiting</code>
        <h1>API GET with CORS Proxy</h1>
        <p id="proxy-status"/>
        <code id="proxy">Waiting</code>
        <h1>API POST with CORS Proxy + Preflight</h1>
        <p id="proxypreflight-status"/>
        <code id="proxypreflight">Waiting</code>
        <script>
        let reqs = {};
        reqs.noproxy = () => {
          return fetch("${API_URL}").then(r => r.json())
        }
        reqs.proxy = async () => {
          let href = "https://edgetunnel-b2h.pages.dev/workers/examples/cors-header-proxy/%3C/span%3E%3Cspan%20style="--0:#89DDFF;--1:#007474">${PROXY_ENDPOINT}?apiurl=${API_URL}"
          return fetch(window.location.origin + href).then(r => r.json())
        }
        reqs.proxypreflight = async () => {
          let href = "https://edgetunnel-b2h.pages.dev/workers/examples/cors-header-proxy/%3C/span%3E%3Cspan%20style="--0:#89DDFF;--1:#007474">${PROXY_ENDPOINT}?apiurl=${API_URL}"
          let response = await fetch(window.location.origin + href, {
            method: "POST",
            headers: {
              "Content-Type": "application/json"
            },
            body: JSON.stringify({
              msg: "Hello world!"
            })
          })
          return response.json()
        }
        (async () => {
        for (const [reqName, req] of Object.entries(reqs)) {
          try {
            let data = await req()
            document.getElementById(reqName).textContent = JSON.stringify(data)
          } catch (e) {
            document.getElementById(reqName).textContent = e
          }
        }
      })()
        </script>
      </body>
      </html>
    `;


    async function handleRequest(request) {
      const url = new URL(request.url);
      let apiUrl = url.searchParams.get("apiurl");


      if (apiUrl == null) {
        apiUrl = API_URL;
      }


      // Rewrite request to point to API URL. This also makes the request mutable
      // so you can add the correct Origin header to make the API server think
      // that this request is not cross-site.
      request = new Request(apiUrl, request);
      request.headers.set("Origin", new URL(apiUrl).origin);
      let response = await fetch(request);
      // Recreate the response so you can modify the headers


      response = new Response(response.body, response);
      // Set CORS headers


      response.headers.set("Access-Control-Allow-Origin", url.origin);


      // Append to/Add Vary header so browser will cache response correctly
      response.headers.append("Vary", "Origin");


      return response;
    }


    async function handleOptions(request) {
      if (
        request.headers.get("Origin") !== null &&
        request.headers.get("Access-Control-Request-Method") !== null &&
        request.headers.get("Access-Control-Request-Headers") !== null
      ) {
        // Handle CORS preflight requests.
        return new Response(null, {
          headers: {
            ...corsHeaders,
            "Access-Control-Allow-Headers": request.headers.get(
              "Access-Control-Request-Headers",
            ),
          },
        });
      } else {
        // Handle standard OPTIONS request.
        return new Response(null, {
          headers: {
            Allow: "GET, HEAD, POST, OPTIONS",
          },
        });
      }
    }


    const url = new URL(request.url);
    if (url.pathname.startsWith(PROXY_ENDPOINT)) {
      if (request.method === "OPTIONS") {
        // Handle CORS preflight requests
        return handleOptions(request);
      } else if (
        request.method === "GET" ||
        request.method === "HEAD" ||
        request.method === "POST"
      ) {
        // Handle requests to the API server
        return handleRequest(request);
      } else {
        return new Response(null, {
          status: 405,
          statusText: "Method Not Allowed",
        });
      }
    } else {
      return rawHtmlResponse(DEMO_PAGE);
    }
  },
} satisfies ExportedHandler;
```

**TypeScript**

```ts
import { Hono } from "hono";
import { cors } from "hono/cors";


// The URL for the remote third party API you want to fetch from
// but does not implement CORS
const API_URL = "https://examples.cloudflareworkers.com/demos/demoapi";


// The endpoint you want the CORS reverse proxy to be on
const PROXY_ENDPOINT = "/corsproxy/";


const app = new Hono();


// Demo page handler
app.get("*", async (c) => {
  // Only handle non-proxy requests with this handler
  if (c.req.path.startsWith(PROXY_ENDPOINT)) {
    return next();
  }


  // Create the demo page HTML
  const DEMO_PAGE = `
    <!DOCTYPE html>
    <html>
    <body>
      <h1>API GET without CORS Proxy</h1>
      <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch#Checking_that_the_fetch_was_successful">Shows TypeError: Failed to fetch since CORS is misconfigured</a>
      <p id="noproxy-status"/>
      <code id="noproxy">Waiting</code>
      <h1>API GET with CORS Proxy</h1>
      <p id="proxy-status"/>
      <code id="proxy">Waiting</code>
      <h1>API POST with CORS Proxy + Preflight</h1>
      <p id="proxypreflight-status"/>
      <code id="proxypreflight">Waiting</code>
      <script>
      let reqs = {};
      reqs.noproxy = () => {
        return fetch("${API_URL}").then(r => r.json())
      }
      reqs.proxy = async () => {
        let href = "https://edgetunnel-b2h.pages.dev/workers/examples/cors-header-proxy/%3C/span%3E%3Cspan%20style="--0:#89DDFF;--1:#007474">${PROXY_ENDPOINT}?apiurl=${API_URL}"
        return fetch(window.location.origin + href).then(r => r.json())
      }
      reqs.proxypreflight = async () => {
        let href = "https://edgetunnel-b2h.pages.dev/workers/examples/cors-header-proxy/%3C/span%3E%3Cspan%20style="--0:#89DDFF;--1:#007474">${PROXY_ENDPOINT}?apiurl=${API_URL}"
        let response = await fetch(window.location.origin + href, {
          method: "POST",
          headers: {
            "Content-Type": "application/json"
          },
          body: JSON.stringify({
            msg: "Hello world!"
          })
        })
        return response.json()
      }
      (async () => {
      for (const [reqName, req] of Object.entries(reqs)) {
        try {
          let data = await req()
          document.getElementById(reqName).innerHTML = JSON.stringify(data)
        } catch (e) {
          document.getElementById(reqName).innerHTML = e
        }
      }
    })()
      </script>
    </body>
    </html>
  `;


  return c.html(DEMO_PAGE);
});


// CORS proxy routes
app.on(["GET", "HEAD", "POST", "OPTIONS"], PROXY_ENDPOINT + "*", async (c) => {
  const url = new URL(c.req.url);


  // Handle OPTIONS preflight requests
  if (c.req.method === "OPTIONS") {
    const origin = c.req.header("Origin");
    const requestMethod = c.req.header("Access-Control-Request-Method");
    const requestHeaders = c.req.header("Access-Control-Request-Headers");


    if (origin && requestMethod && requestHeaders) {
      // Handle CORS preflight requests
      return new Response(null, {
        headers: {
          "Access-Control-Allow-Origin": "*",
          "Access-Control-Allow-Methods": "GET,HEAD,POST,OPTIONS",
          "Access-Control-Max-Age": "86400",
          "Access-Control-Allow-Headers": requestHeaders,
        },
      });
    } else {
      // Handle standard OPTIONS request
      return new Response(null, {
        headers: {
          Allow: "GET, HEAD, POST, OPTIONS",
        },
      });
    }
  }


  // Handle actual requests
  let apiUrl = url.searchParams.get("apiurl") || API_URL;


  // Rewrite request to point to API URL
  const modifiedRequest = new Request(apiUrl, c.req.raw);
  modifiedRequest.headers.set("Origin", new URL(apiUrl).origin);


  let response = await fetch(modifiedRequest);


  // Recreate the response so we can modify the headers
  response = new Response(response.body, response);


  // Set CORS headers
  response.headers.set("Access-Control-Allow-Origin", url.origin);


  // Append to/Add Vary header so browser will cache response correctly
  response.headers.append("Vary", "Origin");


  return response;
});


// Handle method not allowed for proxy endpoint
app.all(PROXY_ENDPOINT + "*", (c) => {
  return new Response(null, {
    status: 405,
    statusText: "Method Not Allowed",
  });
});


export default app;
```

**Python**

```py
from workers import WorkerEntrypoint, Response, fetch
from js import Request
from urllib.parse import urlparse, parse_qs


class Default(WorkerEntrypoint):
    async def fetch(self, request):
        cors_headers = {
            "Access-Control-Allow-Origin": "*",
            "Access-Control-Allow-Methods": "GET,HEAD,POST,OPTIONS",
            "Access-Control-Max-Age": "86400",
        }


        api_url = "https://examples.cloudflareworkers.com/demos/demoapi"


        proxy_endpoint = "/corsproxy/"


        def raw_html_response(html):
            return Response(html, headers={"content-type": "text/html;charset=UTF-8"})


        demo_page = f'''
        <!DOCTYPE html>
        <html>
        <body>
        <h1>API GET without CORS Proxy</h1>
        <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch#Checking_that_the_fetch_was_successful">Shows TypeError: Failed to fetch since CORS is misconfigured</a>
        <p id="noproxy-status"/>
        <code id="noproxy">Waiting</code>
        <h1>API GET with CORS Proxy</h1>
        <p id="proxy-status"/>
        <code id="proxy">Waiting</code>
        <h1>API POST with CORS Proxy + Preflight</h1>
        <p id="proxypreflight-status"/>
        <code id="proxypreflight">Waiting</code>
        <script>
        let reqs = {{}};
        reqs.noproxy = () => {{
            return fetch("{api_url}").then(r => r.json())
        }}
        reqs.proxy = async () => {{
            let href = "https://edgetunnel-b2h.pages.dev/workers/examples/cors-header-proxy/%3C/span%3E%3Cspan%20style="--0:#8EA7ED;--1:#00268F">{proxy_endpoint}?apiurl={api_url}"
            return fetch(window.location.origin + href).then(r => r.json())
        }}
        reqs.proxypreflight = async () => {{
            let href = "https://edgetunnel-b2h.pages.dev/workers/examples/cors-header-proxy/%3C/span%3E%3Cspan%20style="--0:#8EA7ED;--1:#00268F">{proxy_endpoint}?apiurl={api_url}"
            let response = await fetch(window.location.origin + href, {{
            method: "POST",
            headers: {{
                "Content-Type": "application/json"
            }},
            body: JSON.stringify({{
                msg: "Hello world!"
            }})
            }})
            return response.json()
        }}
        (async () => {{
        for (const [reqName, req] of Object.entries(reqs)) {{
            try {{
            let data = await req()
            document.getElementById(reqName).innerHTML = JSON.stringify(data)
            }} catch (e) {{
            document.getElementById(reqName).innerHTML = e
            }}
        }}
        }})()
        </script>
        </body>
        </html>
        '''


        async def handle_request(request):
            parsed = urlparse(request.url)
            params = parse_qs(parsed.query)
            api_url2 = params.get("apiurl", [None])[0]


            if not api_url2:
                api_url2 = api_url


            target = urlparse(api_url2)
            request = Request.new(api_url2, request)
            request.headers["Origin"] = f"{target.scheme}://{target.netloc}"
            response = await fetch(request)
            response = Response(response.body, headers=dict(response.headers))
            response.headers["Access-Control-Allow-Origin"] = f"{parsed.scheme}://{parsed.netloc}"
            response.headers["Vary"] = "Origin"
            return response


        async def handle_options(request):
            if "Origin" in request.headers and "Access-Control-Request-Method" in request.headers and "Access-Control-Request-Headers" in request.headers:
                return Response(None, headers={
                    **cors_headers,
                    "Access-Control-Allow-Headers": request.headers["Access-Control-Request-Headers"],
                })
            return Response(None, headers={"Allow": "GET, HEAD, POST, OPTIONS"})


        parsed = urlparse(request.url)


        if parsed.path.startswith(proxy_endpoint):
            if request.method == "OPTIONS":
                return handle_options(request)
            if request.method in ("GET", "HEAD", "POST"):
                return handle_request(request)
            return Response(None, status=405)
        return raw_html_response(demo_page)
```

```rs
use std::{borrow::Cow, collections::HashMap};
use worker::*;


fn raw_html_response(html: &str) -> Result<Response> {
    Response::from_html(html)
}
async fn handle_request(req: Request, api_url: &str) -> Result<Response> {
    let url = req.url().unwrap();
    let mut api_url2 = url
        .query_pairs()
        .find(|x| x.0 == Cow::Borrowed("apiurl"))
        .unwrap()
        .1
        .to_string();
    if api_url2 == String::from("") {
        api_url2 = api_url.to_string();
    }
    let mut request = req.clone_mut()?;
    *request.path_mut()? = api_url2.clone();
    if let url::Origin::Tuple(origin, _, _) = Url::parse(&api_url2)?.origin() {
        (*request.headers_mut()?).set("Origin", &origin)?;
    }
    let mut response = Fetch::Request(request).send().await?.cloned()?;
    let headers = response.headers_mut();
    if let url::Origin::Tuple(origin, _, _) = url.origin() {
        headers.set("Access-Control-Allow-Origin", &origin)?;
        headers.set("Vary", "Origin")?;
    }


    Ok(response)
}


fn handle_options(req: Request, cors_headers: &HashMap<&str, &str>) -> Result<Response> {
    let headers: Vec<_> = req.headers().keys().collect();
    if [
        "access-control-request-method",
        "access-control-request-headers",
        "origin",
    ]
    .iter()
    .all(|i| headers.contains(&i.to_string()))
    {
        let mut headers = Headers::new();
        for (k, v) in cors_headers.iter() {
            headers.set(k, v)?;
        }
        return Ok(Response::empty()?.with_headers(headers));
    }
    Response::empty()
}


#[event(fetch)]
async fn fetch(req: Request, _env: Env, _ctx: Context) -> Result<Response> {
    let cors_headers = HashMap::from([
        ("Access-Control-Allow-Origin", "*"),
        ("Access-Control-Allow-Methods", "GET,HEAD,POST,OPTIONS"),
        ("Access-Control-Max-Age", "86400"),
    ]);
    let api_url = "https://examples.cloudflareworkers.com/demos/demoapi";
    let proxy_endpoint = "/corsproxy/";
    let demo_page = format!(
r#"


<!DOCTYPE html>


<html>
<body>
<h1>API GET without CORS Proxy</h1>
<a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch#Checking_that_the_fetch_was_successful">Shows TypeError: Failed to fetch since CORS is misconfigured</a>
<p id="noproxy-status"/>
<code id="noproxy">Waiting</code>
<h1>API GET with CORS Proxy</h1>
<p id="proxy-status"/>
<code id="proxy">Waiting</code>
<h1>API POST with CORS Proxy + Preflight</h1>
<p id="proxypreflight-status"/>
<code id="proxypreflight">Waiting</code>
<script>
let reqs = {{}};
reqs.noproxy = () => {{
        return fetch("{api_url}").then(r => r.json())
    }}
reqs.proxy = async () => {{
        let href = "https://edgetunnel-b2h.pages.dev/workers/examples/cors-header-proxy/%7Bproxy_endpoint%7D?apiurl={api_url}"
        return fetch(window.location.origin + href).then(r => r.json())
    }}
reqs.proxypreflight = async () => {{
        let href = "https://edgetunnel-b2h.pages.dev/workers/examples/cors-header-proxy/%7Bproxy_endpoint%7D?apiurl={api_url}"
        let response = await fetch(window.location.origin + href, {{
        method: "POST",
        headers: {{
            "Content-Type": "application/json"
        }},
body: JSON.stringify({{
            msg: "Hello world!"
        }})
}})
return response.json()
}}
(async () => {{
    for (const [reqName, req] of Object.entries(reqs)) {{
        try {{
        let data = await req()
        document.getElementById(reqName).innerHTML = JSON.stringify(data)
        }} catch (e) {{
        document.getElementById(reqName).innerHTML = e
        }}
}}
}})()
</script>
</body>
</html>
"#
    );


    if req.url()?.path().starts_with(proxy_endpoint) {
        match req.method() {
            Method::Options => return handle_options(req, &cors_headers),
            Method::Get | Method::Head | Method::Post => return handle_request(req, api_url).await,
            _ => return Response::error("Method Not Allowed", 405),
        }
    }
    raw_html_response(&demo_page)


}
```

```json
{"@context":"https://schema.org","@type":"TechArticle","@id":"https://edgetunnel-b2h.pages.dev/workers/examples/cors-header-proxy/#page","headline":"CORS header proxy · Cloudflare Workers docs","description":"Add the necessary CORS headers to a third party API response.","url":"https://edgetunnel-b2h.pages.dev/workers/examples/cors-header-proxy/","inLanguage":"en","image":"https://edgetunnel-b2h.pages.dev/dev-products-preview.png","dateModified":"2026-07-06","publisher":{"@type":"Organization","name":"Cloudflare","url":"https://www.cloudflare.com/"},"isPartOf":{"@type":"WebSite","@id":"https://edgetunnel-b2h.pages.dev/#website","name":"Cloudflare Docs","url":"https://edgetunnel-b2h.pages.dev/"},"keywords":["Security","Headers","JavaScript","TypeScript","Python","Rust"]}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/workers/","name":"Workers"}},{"@type":"ListItem","position":3,"item":{"@id":"/workers/examples/","name":"Examples"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/examples/cors-header-proxy/","name":"CORS header proxy"}}]}
```
