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

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/workers/llms.txt  
> Use this file to discover all available pages before exploring further.

[Skip to content](#%5Ftop) 

### Tags

[ Security ](https://developers.cloudflare.com/search/?tags=Security)[ Headers ](https://developers.cloudflare.com/search/?tags=Headers)[ JavaScript ](https://developers.cloudflare.com/search/?tags=JavaScript)[ TypeScript ](https://developers.cloudflare.com/search/?tags=TypeScript)[ Python ](https://developers.cloudflare.com/search/?tags=Python)[ Rust ](https://developers.cloudflare.com/search/?tags=Rust) 

# CORS header proxy

**Last reviewed:**  over 5 years ago 

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-9260)
* [  TypeScript ](#tab-panel-9261)
* [  Hono ](#tab-panel-9262)
* [  Python ](#tab-panel-9263)
* [  Rust ](#tab-panel-9264)

JavaScript

```

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://developers.cloudflare.com/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://developers.cloudflare.com/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

```

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://developers.cloudflare.com/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://developers.cloudflare.com/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

```

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://developers.cloudflare.com/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://developers.cloudflare.com/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

```

from workers import WorkerEntrypoint

from pyodide.ffi import to_js as _to_js

from js import Response, URL, fetch, Object, Request


def to_js(x):

    return _to_js(x, dict_converter=Object.fromEntries)


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.new(html, headers=to_js({"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://developers.cloudflare.com/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://developers.cloudflare.com/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):

            url = URL.new(request.url)

            api_url2 = url.searchParams["apiurl"]


            if not api_url2:

                api_url2 = api_url


            request = Request.new(api_url2, request)

            request.headers["Origin"] = (URL.new(api_url2)).origin

            print(request.headers)

            response = await fetch(request)

            response = Response.new(response.body, response)

            response.headers["Access-Control-Allow-Origin"] = url.origin

            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.new(None, headers=to_js({

                **cors_headers,

                "Access-Control-Allow-Headers": request.headers["Access-Control-Request-Headers"]

                }))

            return Response.new(None, headers=to_js({"Allow": "GET, HEAD, POST, OPTIONS"}))


        url = URL.new(request.url)


        if url.pathname.startswith(proxy_endpoint):

            if request.method == "OPTIONS":

                return handle_options(request)

            if request.method in ("GET", "HEAD", "POST"):

                return handle_request(request)

            return Response.new(None, status=405, statusText="Method Not Allowed")

        return raw_html_response(demo_page)


```

```

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://developers.cloudflare.com/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://developers.cloudflare.com/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":"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"}}]}
```
