Skip to content
Workers
Visit Workers on GitHub
Set theme to dark (⇧+D)

Examples

Respond to the Worker request with the response from another website (example.com in this example).

addEventListener("fetch", event => {  return event.respondWith(    fetch("https://example.com")  )})

Cache using Cloudflare's Cache API. This example can cache POST requests as well.

const someOtherHostname = "my.herokuapp.com"
async function handleRequest(event) {  const request = event.request  const cacheUrl = new URL(request.url)
  // Hostname for a different zone  cacheUrl.hostname = someOtherHostname
  const cacheKey = new Request(cacheUrl.toString(), request)  const cache = caches.default
  // Get this request from this zone's cache  let response = await cache.match(cacheKey)
  if (!response) {    //If not in cache, get it from origin    response = await fetch(request)
    // Must use Response constructor to inherit all of response's fields    response = new Response(response.body, response)
    // Cache API respects Cache-Control headers. Setting max-age to 10    // will limit the response to be in cache for 10 seconds max    response.headers.append("Cache-Control", "max-age=10")
    // Store the fetched response as cacheKey    // Use waitUntil so computational expensive tasks don"t delay the response    event.waitUntil(cache.put(cacheKey, response.clone()))  }  return response}
async function sha256(message) {  // encode as UTF-8  const msgBuffer = new TextEncoder().encode(message)
  // hash the message  const hashBuffer = await crypto.subtle.digest("SHA-256", msgBuffer)
  // convert ArrayBuffer to Array  const hashArray = Array.from(new Uint8Array(hashBuffer))
  // convert bytes to hex string  const hashHex = hashArray.map(b => ("00" + b.toString(16)).slice(-2)).join("")  return hashHex}
async function handlePostRequest(event) {  const request = event.request  const body = await request.clone().text()  const hash = await sha256(body)  const cacheUrl = new URL(request.url)
  // Store the URL in cache by prepending the body's hash  cacheUrl.pathname = "/posts" + cacheUrl.pathname + hash
  // Convert to a GET to be able to cache  const cacheKey = new Request(cacheUrl.toString(), {    headers: request.headers,    method: "GET",  })
  const cache = caches.default
  //Find the cache key in the cache  let response = await cache.match(cacheKey)
  // Otherwise, fetch response to POST request from origin  if (!response) {    response = await fetch(request)    event.waitUntil(cache.put(cacheKey, response.clone()))  }  return response}
addEventListener("fetch", event => {  try {    const request = event.request    if (request.method.toUpperCase() === "POST")      return event.respondWith(handlePostRequest(event))    return event.respondWith(handleRequest(event))  } catch (e) {    return event.respondWith(new Response("Error thrown " + e.message))  }})

Return a response based on the incoming request's URL, HTTP method, User Agent, IP address, ASN or device type.

const BLOCKED_HOSTNAMES = ["nope.mywebsite.com", "bye.website.com"]
async function handleRequest(request) {  // Return a new Response based on a URL's hostname  const url = new URL(request.url)
  if (BLOCKED_HOSTNAMES.includes(url.hostname)) {    return new Response("Blocked Host", { status: 403 })  }
  // Block paths ending in .doc or .xml based on the URL's file extension  const forbiddenExtRegExp = new RegExp(/\.(doc|xml)$/)
  if (forbiddenExtRegExp.test(url.pathname)) {    return new Response("Blocked Extension", { status: 403 })  }
  // On HTTP method  if (request.method === "POST") {    return new Response("Response for POST")  }
  // On User Agent  const userAgent = request.headers.get("User-Agent") || ""  if (userAgent.includes("bot")) {    return new Response("Block User Agent containing bot", { status: 403 })  }
  // On Client's IP address  const clientIP = request.headers.get("CF-Connecting-IP")  if (clientIP === "1.2.3.4") {    return new Response("Block the IP 1.2.3.4", { status: 403 })  }
  // On ASN  if (request.cf && request.cf.asn == 64512) {    return new Response("Block the ASN 64512 response")  }
  // On Device Type  // Requires Enterprise "CF-Device-Type Header" zone setting or  // Page Rule with "Cache By Device Type" setting applied.  const device = request.headers.get("CF-Device-Type")  if (device === "mobile") {    return Response.redirect("https://mobile.example.com")  }
  console.error(    "Getting Client's IP address, device type, and ASN are not supported in playground. Must test on a live worker",  )  return fetch(request)}
addEventListener("fetch", event => {  event.respondWith(handleRequest(event.request))})

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

// We support the GET, POST, HEAD, and OPTIONS methods from any origin,// and accept the Content-Type header on requests. These headers must be// present on all responses to all CORS requests. In practice, this means// all responses to OPTIONS requests.const corsHeaders = {  "Access-Control-Allow-Origin": "*",  "Access-Control-Allow-Methods": "GET, HEAD, POST, OPTIONS",  "Access-Control-Allow-Headers": "Content-Type",  "Access-Control-Max-Age": "86400",}
// The URL for the remote third party API you want to fetch from// but does not implement CORSconst API_URL = "https://workers-tooling.cf/demos/demoapi"
// The endpoint you want the CORS reverse proxy to be onconst PROXY_ENDPOINT = "/corsproxy/"
// The rest of this snippet for the demo pageasync 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 = async () => {      let response = await fetch("${API_URL}")      return await response.json()    }    reqs.proxy = async () => {      let response = await fetch(window.location.origin + "${PROXY_ENDPOINT}?apiurl=${API_URL}")      return await response.json()    }    reqs.proxypreflight = async () => {      const reqBody = {        msg: "Hello world!"      }      let response = await fetch(window.location.origin + "${PROXY_ENDPOINT}?apiurl=${API_URL}", {        method: "POST",        headers: {          "Content-Type": "application/json"        },        body: JSON.stringify(reqBody),      })      return await 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 we can add the correct Origin header to make the API server think  // that this request isn't cross-site.  request = new Request(apiUrl, request)  request.headers.set("Origin", new URL(apiUrl).origin)  let response = await fetch(request)
  // 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}
function handleOptions(request) {  // Make sure the necessary headers are present  // for this to be a valid pre-flight 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 pre-flight request.    // If you want to check the requested method + headers    // you can do that here.    return new Response(null, {      headers: corsHeaders,    })  }  else {    // Handle standard OPTIONS request.    // If you want to allow other HTTP Methods, you can do that here.    return new Response(null, {      headers: {        Allow: "GET, HEAD, POST, OPTIONS",      },    })  }}
addEventListener("fetch", event => {  const request = event.request  const url = new URL(request.url)  if(url.pathname.startsWith(PROXY_ENDPOINT)){    if (request.method === "OPTIONS") {      // Handle CORS preflight requests      event.respondWith(handleOptions(request))    }    else if(      request.method === "GET" ||      request.method === "HEAD" ||      request.method === "POST"    ){      // Handle requests to the API server      event.respondWith(handleRequest(request))    }    else {      event.respondWith(        new Response(null, {          status: 405,          statusText: "Method Not Allowed",        }),      )    }  }  else {    // Serve demo page    event.respondWith(rawHtmlResponse(DEMO_PAGE))  }})

Protect sensitive data to prevent data loss, and send alerts to a webhooks server in the event of a data breach.

const DEBUG = trueconst SOME_HOOK_SERVER = "https://webhook.flow-wolf.io/hook"/** * Alert a data breach by posting to a webhook server */async function postDataBreach(request) {  const trueClientIp = request.headers.get("cf-connecting-ip")  const epoch = new Date().getTime()  const body = {    ip: trueClientIp,    time: epoch,    request: request  }  const init = {    body: JSON.stringify(body),    method: "POST",    headers: {      "content-type": "application/json;charset=UTF-8"    },  }  return await fetch(SOME_HOOK_SERVER, init)}/** * Define personal data with regular expressions. * Respond with block if credit card data, and strip * emails and phone numbers from the response. * Execution will be limited to MIME type "text/*". */async function handleRequest(request) {  const response = await fetch(request)
  // Return origin response, if response wasn’t text  const contentType = response.headers.get("content-type") || ""  if (!contentType.toLowerCase().includes("text/")) {    return response  }
  let text = await response.text()
  // When debugging replace the response  // from the origin with an email  text = DEBUG    ? text.replace("You may use this", "me@example.com may use this")    : text
  const sensitiveRegexsMap = {    creditCard: String.raw`\b(?:4[0-9]{12}(?:[0-9]{3})?|(?:5[1-5][0-9]{2}|222[1-9]|22[3-9][0-9]|2[3-6][0-9]{2}|27[01][0-9]|2720)[0-9]{12}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|6(?:011|5[0-9]{2})[0-9]{12}|(?:2131|1800|35\d{3})\d{11})\b`,    email: String.raw`\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b`,    phone: String.raw`\b07\d{9}\b`  }
  for (const kind in sensitiveRegexsMap) {    const sensitiveRegex = new RegExp(sensitiveRegexsMap[kind], "ig")    const match = await sensitiveRegex.test(text)    if (match) {      // Alert a data breach      await postDataBreach(request)
      // Respond with a block if credit card,      // otherwise replace sensitive text with `*`s      return kind === "creditCard"        ? new Response(kind + " found\nForbidden\n", {            status: 403,            statusText: "Forbidden"          })        : new Response(text.replace(sensitiveRegex, "**********"), response)    }  }  return new Response(text, response)}
addEventListener("fetch", event => {  event.respondWith(handleRequest(event.request))})

Create a modified request with edited properties based off of an incoming request.

/** * Example someHost is set up to return raw JSON * @param {string} someUrl the URL to send the request to, since we are setting hostname too only path is applied * @param {string} someHost the host the request will resolve too */const someHost = "example.com"const someUrl = "https://foo.example.com/api.js"
async function handleRequest(request) {  /**   * The best practice is to only assign new properties on the request   * object (i.e. RequestInit props) using either a method or the constructor   */  const newRequestInit = {    // Change method    method: "POST",    // Change body    body: JSON.stringify({ bar: "foo" }),    // Change the redirect mode.    redirect: "follow",    //Change headers, note this method will erase existing headers    headers: {      "Content-Type": "application/json",    },    // Change a Cloudflare feature on the outbound response    cf: { apps: false },  }
  // Change just the host  const url = new URL(someUrl)
  url.hostname = someHost
  // Best practice is to always use the original request to construct the new request  // to clone all the attributes. Applying the URL also requires a constructor  // since once a Request has been constructed, its URL is immutable.  const newRequest = new Request(    url.toString(),    new Request(request, newRequestInit),  )
  // Set headers using method  newRequest.headers.set("X-Example", "bar")  newRequest.headers.set("Content-Type", "application/json")  try {    return await fetch(newRequest)  } catch (e) {    return new Response(JSON.stringify({ error: e.message }), { status: 500 })  }}
addEventListener("fetch", event => {  event.respondWith(handleRequest(event.request))})

Serve an HTML form, then read POST requests. Use also to read JSON or POST data from an incoming request.

/** * rawHtmlResponse returns HTML inputted directly * into the worker script * @param {string} html */function rawHtmlResponse(html) {  const init = {    headers: {      "content-type": "text/html;charset=UTF-8",    },  }  return new Response(html, init)}/** * readRequestBody reads in the incoming request body * Use await readRequestBody(..) in an async function to get the string * @param {Request} request the incoming request to read from */async function readRequestBody(request) {  const { headers } = request  const contentType = headers.get("content-type") || ""
  if (contentType.includes("application/json")) {    return JSON.stringify(await request.json())  }  else if (contentType.includes("application/text")) {    return await request.text()  }  else if (contentType.includes("text/html")) {    return await request.text()  }  else if (contentType.includes("form")) {    const formData = await request.formData()    const body = {}    for (const entry of formData.entries()) {      body[entry[0]] = entry[1]    }    return JSON.stringify(body)  }  else {    const myBlob = await request.blob()    const objectURL = URL.createObjectURL(myBlob)    return objectURL  }}
const someForm = `  <!DOCTYPE html>  <html>  <body>  <h1>Hello World</h1>  <p>This is all generated using a Worker</p>  <form action="/demos/requests" method="post">    <div>      <label for="say">What  do you want to say?</label>      <input name="say" id="say" value="Hi">    </div>    <div>      <label for="to">To who?</label>      <input name="to" id="to" value="Mom">    </div>    <div>      <button>Send my greetings</button>    </div>  </form>  </body>  </html>  `
async function handleRequest(request) {  const reqBody = await readRequestBody(request)  const retBody = `The request body sent in was ${reqBody}`  return new Response(retBody)}
addEventListener("fetch", event => {  const { request } = event  const { url } = request
  if (url.includes("form")) {    return event.respondWith(rawHtmlResponse(someForm))  }  if (request.method === "POST") {    return event.respondWith(handleRequest(request))  }  else if (request.method === "GET") {    return event.respondWith(new Response(`The request was a GET`))  }})

Sign and verify a request using the HMAC and SHA-256 algorithms or return a 403.

// We will need some super-secret data to use as a symmetric key.const encoder = new TextEncoder()const secretKeyData = encoder.encode("my secret symmetric key")
// Convert a ByteString (a string whose code units are all in the range// [0, 255]), to a Uint8Array. If you pass in a string with code units larger// than 255, their values will overflow!function byteStringToUint8Array(byteString) {  const ui = new Uint8Array(byteString.length)  for (let i = 0; i < byteString.length; ++i) {    ui[i] = byteString.charCodeAt(i)  }  return ui}
async function verifyAndFetch(request) {  const url = new URL(request.url)
  // If the path does not begin with our protected prefix, just pass the request  // through.  if (!url.pathname.startsWith("/verify/")) {    return fetch(request)  }
  // Make sure we have the minimum necessary query parameters.  if (!url.searchParams.has("mac") || !url.searchParams.has("expiry")) {    return new Response("Missing query parameter", { status: 403 })  }
  const key = await crypto.subtle.importKey(    "raw",    secretKeyData,    { name: "HMAC", hash: "SHA-256" },    false,    ["verify"],  )
  // Extract the query parameters we need and run the HMAC algorithm on the  // parts of the request we are authenticating: the path and the expiration  // timestamp.  const expiry = Number(url.searchParams.get("expiry"))  const dataToAuthenticate = url.pathname + expiry
  // The received MAC is Base64-encoded, so we have to go to some trouble to  // get it into a buffer type that crypto.subtle.verify() can read.  const receivedMacBase64 = url.searchParams.get("mac")  const receivedMac = byteStringToUint8Array(atob(receivedMacBase64))
  // Use crypto.subtle.verify() to guard against timing attacks. Since HMACs use  // symmetric keys, we could implement this by calling crypto.subtle.sign() and  // then doing a string comparison -- this is insecure, as string comparisons  // bail out on the first mismatch, which leaks information to potential  // attackers.  const verified = await crypto.subtle.verify(    "HMAC",    key,    receivedMac,    encoder.encode(dataToAuthenticate),  )
  if (!verified) {    const body = "Invalid MAC"    return new Response(body, { status: 403 })  }
  if (Date.now() > expiry) {    const body = `URL expired at ${new Date(expiry)}`    return new Response(body, { status: 403 })  }
  // We have verified the MAC and expiration time; we are good to pass the request  // through.  return fetch(request)}
addEventListener("fetch", event => {  event.respondWith(verifyAndFetch(event.request))})