--- title: Cloudflare Workers description: With Cloudflare Workers, you can expect to: image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/index.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Cloudflare Workers A serverless platform for building, deploying, and scaling apps across[Cloudflare's global network ↗](https://www.cloudflare.com/network/) with a single command — no infrastructure to manage, no complex configuration With Cloudflare Workers, you can expect to: * Deliver fast performance with high reliability anywhere in the world * Build full-stack apps with your framework of choice, including [React](https://developers.cloudflare.com/workers/framework-guides/web-apps/react/), [Vue](https://developers.cloudflare.com/workers/framework-guides/web-apps/vue/), [Svelte](https://developers.cloudflare.com/workers/framework-guides/web-apps/sveltekit/), [Next](https://developers.cloudflare.com/workers/framework-guides/web-apps/nextjs/), [Astro](https://developers.cloudflare.com/workers/framework-guides/web-apps/astro/), [React Router](https://developers.cloudflare.com/workers/framework-guides/web-apps/react-router/), [and more](https://developers.cloudflare.com/workers/framework-guides/) * Use your preferred language, including [JavaScript](https://developers.cloudflare.com/workers/languages/javascript/), [TypeScript](https://developers.cloudflare.com/workers/languages/typescript/), [Python](https://developers.cloudflare.com/workers/languages/python/), [Rust](https://developers.cloudflare.com/workers/languages/rust/), [and more](https://developers.cloudflare.com/workers/runtime-apis/webassembly/) * Gain deep visibility and insight with built-in [observability](https://developers.cloudflare.com/workers/observability/logs/) * Get started for free and grow with flexible [pricing](https://developers.cloudflare.com/workers/platform/pricing/), affordable at any scale Get started with your first project: [ Deploy a template ](https://dash.cloudflare.com/?to=/:account/workers-and-pages/templates) [ Deploy with Wrangler CLI ](https://developers.cloudflare.com/workers/get-started/guide/) --- ## Build with Workers #### Front-end applications Deploy [static assets](https://developers.cloudflare.com/workers/static-assets/) to Cloudflare's [CDN & cache](https://developers.cloudflare.com/cache/) for fast rendering #### Back-end applications Build APIs and connect to data stores with [Smart Placement](https://developers.cloudflare.com/workers/configuration/placement/) to optimize latency #### Serverless AI inference Run LLMs, generate images, and more with [Workers AI](https://developers.cloudflare.com/workers-ai/) #### Background jobs Schedule [cron jobs](https://developers.cloudflare.com/workers/configuration/cron-triggers/), run durable [Workflows](https://developers.cloudflare.com/workflows/), and integrate with [Queues](https://developers.cloudflare.com/queues/) #### Observability & monitoring Monitor performance, debug issues, and analyze traffic with [real-time logs](https://developers.cloudflare.com/workers/observability/logs/) and [analytics](https://developers.cloudflare.com/workers/observability/metrics-and-analytics/) --- ## Integrate with Workers Connect to external services like databases, APIs, and storage via [Bindings](https://developers.cloudflare.com/workers/runtime-apis/bindings/), enabling functionality with just a few lines of code: **Storage** **[Durable Objects](https://developers.cloudflare.com/durable-objects/)** Scalable stateful storage for real-time coordination. **[D1](https://developers.cloudflare.com/d1/)** Serverless SQL database built for fast, global queries. **[KV](https://developers.cloudflare.com/kv/)** Low-latency key-value storage for fast, edge-cached reads. **[Queues](https://developers.cloudflare.com/queues/)** Guaranteed delivery with no charges for egress bandwidth. **[Hyperdrive](https://developers.cloudflare.com/hyperdrive/)** Connect to your external database with accelerated queries, cached at the edge. **Compute** **[Workers AI](https://developers.cloudflare.com/workers-ai/)** Machine learning models powered by serverless GPUs. **[Workflows](https://developers.cloudflare.com/workflows/)** Durable, long-running operations with automatic retries. **[Vectorize](https://developers.cloudflare.com/vectorize/)** Vector database for AI-powered semantic search. **[R2](https://developers.cloudflare.com/r2/)** Zero-egress object storage for cost-efficient data access. **[Browser Rendering](https://developers.cloudflare.com/browser-rendering/)** Programmatic serverless browser instances. **Media** **[Cache / CDN](https://developers.cloudflare.com/cache/)** Global caching for high-performance, low-latency delivery. **[Images](https://developers.cloudflare.com/images/)** Streamlined image infrastructure from a single API. --- Want to connect with the Workers community? [Join our Discord ↗](https://discord.cloudflare.com) ```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"}}]} ``` --- --- title: Examples description: Explore the following examples for Workers. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/examples/index.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Examples Explore the following examples for Workers. Filter resources... [Single Page App (SPA) shell with bootstrap dataUse HTMLRewriter to inject prefetched bootstrap data into an SPA shell, eliminating client-side data fetching on initial load. Works with Workers Static Assets or an externally hosted SPA.](https://developers.cloudflare.com/workers/examples/spa-shell/)[Write to Analytics EngineWrite custom analytics events to Workers Analytics Engine for high-cardinality, time-series data.](https://developers.cloudflare.com/workers/examples/analytics-engine/)[Stream large JSONParse and transform large JSON request and response bodies using streaming.](https://developers.cloudflare.com/workers/examples/streaming-json/)[HTTP Basic AuthenticationShows how to restrict access using the HTTP Basic schema.](https://developers.cloudflare.com/workers/examples/basic-auth/)[Fetch HTMLSend a request to a remote server, read HTML from the response, and serve that HTML.](https://developers.cloudflare.com/workers/examples/fetch-html/)[Return small HTML pageDeliver an HTML page from an HTML string directly inside the Worker script.](https://developers.cloudflare.com/workers/examples/return-html/)[Return JSONReturn JSON directly from a Worker script, useful for building APIs and middleware.](https://developers.cloudflare.com/workers/examples/return-json/)[Sign requestsVerify a signed request using the HMAC and SHA-256 algorithms or return a 403.](https://developers.cloudflare.com/workers/examples/signing-requests/)[Stream OpenAI API ResponsesUse the OpenAI v4 SDK to stream responses from OpenAI.](https://developers.cloudflare.com/workers/examples/openai-sdk-streaming/)[Using timingSafeEqualProtect against timing attacks by safely comparing values using timingSafeEqual.](https://developers.cloudflare.com/workers/examples/protect-against-timing-attacks/)[Turnstile with WorkersInject Turnstile implicitly into HTML elements using the HTMLRewriter runtime API.](https://developers.cloudflare.com/workers/examples/turnstile-html-rewriter/)[Custom Domain with ImagesSet up custom domain for Images using a Worker or serve images using a prefix path and Cloudflare registered domain.](https://developers.cloudflare.com/workers/examples/images-workers/)[103 Early HintsAllow a client to request static assets while waiting for the HTML response.](https://developers.cloudflare.com/workers/examples/103-early-hints/)[Cache Tags using WorkersSend Additional Cache Tags using Workers](https://developers.cloudflare.com/workers/examples/cache-tags/)[Accessing the Cloudflare ObjectAccess custom Cloudflare properties and control how Cloudflare features are applied to every request.](https://developers.cloudflare.com/workers/examples/accessing-the-cloudflare-object/)[Aggregate requestsSend two GET request to two urls and aggregates the responses into one response.](https://developers.cloudflare.com/workers/examples/aggregate-requests/)[Block on TLSInspects the incoming request's TLS version and blocks if under TLSv1.2.](https://developers.cloudflare.com/workers/examples/block-on-tls/)[Bulk redirectsRedirect requests to certain URLs based on a mapped object to the request's URL.](https://developers.cloudflare.com/workers/examples/bulk-redirects/)[Cache POST requestsCache POST requests using the Cache API.](https://developers.cloudflare.com/workers/examples/cache-post-request/)[Conditional responseReturn a response based on the incoming request's URL, HTTP method, User Agent, IP address, ASN or device type.](https://developers.cloudflare.com/workers/examples/conditional-response/)[Cookie parsingGiven the cookie name, get the value of a cookie. You can also use cookies for A/B testing.](https://developers.cloudflare.com/workers/examples/extract-cookie-value/)[Fetch JSONSend a GET request and read in JSON from the response. Use to fetch external data.](https://developers.cloudflare.com/workers/examples/fetch-json/)[Geolocation: Custom StylingPersonalize website styling based on localized user time.](https://developers.cloudflare.com/workers/examples/geolocation-custom-styling/)[Geolocation: Hello WorldGet all geolocation data fields and display them in HTML.](https://developers.cloudflare.com/workers/examples/geolocation-hello-world/)[Post JSONSend a POST request with JSON data. Use to share data with external servers.](https://developers.cloudflare.com/workers/examples/post-json/)[RedirectRedirect requests from one URL to another or from one set of URLs to another set.](https://developers.cloudflare.com/workers/examples/redirect/)[Rewrite linksRewrite URL links in HTML using the HTMLRewriter. This is useful for JAMstack websites.](https://developers.cloudflare.com/workers/examples/rewrite-links/)[Set security headersSet common security headers (X-XSS-Protection, X-Frame-Options, X-Content-Type-Options, Permissions-Policy, Referrer-Policy, Strict-Transport-Security, Content-Security-Policy).](https://developers.cloudflare.com/workers/examples/security-headers/)[Multiple Cron TriggersSet multiple Cron Triggers on three different schedules.](https://developers.cloudflare.com/workers/examples/multiple-cron-triggers/)[Setting Cron TriggersSet a Cron Trigger for your Worker.](https://developers.cloudflare.com/workers/examples/cron-trigger/)[Using the WebSockets APIUse the WebSockets API to communicate in real time with your Cloudflare Workers.](https://developers.cloudflare.com/workers/examples/websockets/)[Geolocation: Weather applicationFetch weather data from an API using the user's geolocation data.](https://developers.cloudflare.com/workers/examples/geolocation-app-weather/)[A/B testing with same-URL direct accessSet up an A/B test by controlling what response is served based on cookies. This version supports passing the request through to test and control on the origin, bypassing random assignment.](https://developers.cloudflare.com/workers/examples/ab-testing/)[Alter headersExample of how to add, change, or delete headers sent in a request or returned in a response.](https://developers.cloudflare.com/workers/examples/alter-headers/)[Auth with headersAllow or deny a request based on a known pre-shared key in a header. This is not meant to replace the WebCrypto API.](https://developers.cloudflare.com/workers/examples/auth-with-headers/)[Bulk origin overrideResolve requests to your domain to a set of proxy third-party origin URLs.](https://developers.cloudflare.com/workers/examples/bulk-origin-proxy/)[Using the Cache APIUse the Cache API to store responses in Cloudflare's cache.](https://developers.cloudflare.com/workers/examples/cache-api/)[Cache using fetchDetermine how to cache a resource by setting TTLs, custom cache keys, and cache headers in a fetch request.](https://developers.cloudflare.com/workers/examples/cache-using-fetch/)[CORS header proxyAdd the necessary CORS headers to a third party API response.](https://developers.cloudflare.com/workers/examples/cors-header-proxy/)[Country code redirectRedirect a response based on the country code in the header of a visitor.](https://developers.cloudflare.com/workers/examples/country-code-redirect/)[Data loss preventionProtect sensitive data to prevent data loss, and send alerts to a webhooks server in the event of a data breach.](https://developers.cloudflare.com/workers/examples/data-loss-prevention/)[Debugging logsSend debugging information in an errored response to a logging service.](https://developers.cloudflare.com/workers/examples/debugging-logs/)[Hot-link protectionBlock other websites from linking to your content. This is useful for protecting images.](https://developers.cloudflare.com/workers/examples/hot-link-protection/)[Logging headers to consoleExamine the contents of a Headers object by logging to console with a Map.](https://developers.cloudflare.com/workers/examples/logging-headers/)[Modify request propertyCreate a modified request with edited properties based off of an incoming request.](https://developers.cloudflare.com/workers/examples/modify-request-property/)[Modify responseFetch and modify response properties which are immutable by creating a copy first.](https://developers.cloudflare.com/workers/examples/modify-response/)[Read POSTServe an HTML form, then read POST requests. Use also to read JSON or POST data from an incoming request.](https://developers.cloudflare.com/workers/examples/read-post/)[Respond with another siteRespond to the Worker request with the response from another website (example.com in this example).](https://developers.cloudflare.com/workers/examples/respond-with-another-site/) ```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"}}]} ``` --- --- title: 103 Early Hints description: Allow a client to request static assets while waiting for the HTML response. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) ### Tags [ Middleware ](https://developers.cloudflare.com/search/?tags=Middleware)[ 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) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/examples/103-early-hints.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # 103 Early Hints **Last reviewed:** over 3 years ago Allow a client to request static assets while waiting for the HTML 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/103-early-hints) This creates a repository in your GitHub account and deploys the application to Cloudflare Workers. `103` Early Hints is an HTTP status code designed to speed up content delivery. When enabled, Cloudflare can cache the `Link` headers marked with preload and/or preconnect from HTML pages and serve them in a `103` Early Hints response before reaching the origin server. Browsers can use these hints to fetch linked assets while waiting for the origin’s final response, dramatically improving page load speeds. To ensure Early Hints are enabled on your zone: 1. In the Cloudflare dashboard, go to the **Speed settings** page. [ Go to **Settings** ](https://dash.cloudflare.com/?to=/:account/:zone/speed/optimization) 2. Go to **Content Optimization**. 3. Enable the **Early Hints** toggle to on. You can return `Link` headers from a Worker running on your zone to speed up your page load times. * [ JavaScript ](#tab-panel-7345) * [ TypeScript ](#tab-panel-7346) * [ Python ](#tab-panel-7347) * [ Hono ](#tab-panel-7348) JavaScript ``` const CSS = "body { color: red; }"; const HTML = ` Early Hints test

Early Hints test page

`; export default { async fetch(req) { // If request is for test.css, serve the raw CSS if (/test\.css$/.test(req.url)) { return new Response(CSS, { headers: { "content-type": "text/css", }, }); } else { // Serve raw HTML using Early Hints for the CSS file return new Response(HTML, { headers: { "content-type": "text/html", link: "; rel=preload; as=style", }, }); } }, }; ``` Explain Code JavaScript ``` const CSS = "body { color: red; }"; const HTML = ` Early Hints test

Early Hints test page

`; export default { async fetch(req): Promise { // If request is for test.css, serve the raw CSS if (/test\.css$/.test(req.url)) { return new Response(CSS, { headers: { "content-type": "text/css", }, }); } else { // Serve raw HTML using Early Hints for the CSS file return new Response(HTML, { headers: { "content-type": "text/html", link: "; rel=preload; as=style", }, }); } }, } satisfies ExportedHandler; ``` Explain Code Python ``` import re from workers import Response, WorkerEntrypoint CSS = "body { color: red; }" HTML = """ Early Hints test

Early Hints test page

""" class Default(WorkerEntrypoint): async def fetch(self, request): if re.search("test.css", request.url): headers = {"content-type": "text/css"} return Response(CSS, headers=headers) else: headers = {"content-type": "text/html","link": "; rel=preload; as=style"} return Response(HTML, headers=headers) ``` Explain Code TypeScript ``` import { Hono } from "hono"; const app = new Hono(); const CSS = "body { color: red; }"; const HTML = ` Early Hints test

Early Hints test page

`; // Serve CSS file app.get("/test.css", (c) => { return c.body(CSS, { headers: { "content-type": "text/css", }, }); }); // Serve HTML with early hints app.get("*", (c) => { return c.html(HTML, { headers: { link: "; rel=preload; as=style", }, }); }); export default app; ``` Explain Code ```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/103-early-hints/","name":"103 Early Hints"}}]} ``` --- --- title: A/B testing with same-URL direct access description: Set up an A/B test by controlling what response is served based on cookies. This version supports passing the request through to test and control on the origin, bypassing random assignment. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) ### Tags [ JavaScript ](https://developers.cloudflare.com/search/?tags=JavaScript)[ TypeScript ](https://developers.cloudflare.com/search/?tags=TypeScript)[ Python ](https://developers.cloudflare.com/search/?tags=Python) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/examples/ab-testing.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # A/B testing with same-URL direct access **Last reviewed:** over 5 years ago Set up an A/B test by controlling what response is served based on cookies. This version supports passing the request through to test and control on the origin, bypassing random assignment. * [ JavaScript ](#tab-panel-7349) * [ TypeScript ](#tab-panel-7350) * [ Python ](#tab-panel-7351) * [ Hono ](#tab-panel-7352) JavaScript ``` const NAME = "myExampleWorkersABTest"; export default { async fetch(req) { const url = new URL(req.url); // Enable Passthrough to allow direct access to control and test routes. if (url.pathname.startsWith("/control") || url.pathname.startsWith("/test")) return fetch(req); // Determine which group this requester is in. const cookie = req.headers.get("cookie"); if (cookie && cookie.includes(`${NAME}=control`)) { url.pathname = "/control" + url.pathname; } else if (cookie && cookie.includes(`${NAME}=test`)) { url.pathname = "/test" + url.pathname; } else { // If there is no cookie, this is a new client. Choose a group and set the cookie. const group = Math.random() < 0.5 ? "test" : "control"; // 50/50 split if (group === "control") { url.pathname = "/control" + url.pathname; } else { url.pathname = "/test" + url.pathname; } // Reconstruct response to avoid immutability let res = await fetch(url); res = new Response(res.body, res); // Set cookie to enable persistent A/B sessions. res.headers.append("Set-Cookie", `${NAME}=${group}; path=/`); return res; } return fetch(url); }, }; ``` Explain Code TypeScript ``` const NAME = "myExampleWorkersABTest"; export default { async fetch(req): Promise { const url = new URL(req.url); // Enable Passthrough to allow direct access to control and test routes. if (url.pathname.startsWith("/control") || url.pathname.startsWith("/test")) return fetch(req); // Determine which group this requester is in. const cookie = req.headers.get("cookie"); if (cookie && cookie.includes(`${NAME}=control`)) { url.pathname = "/control" + url.pathname; } else if (cookie && cookie.includes(`${NAME}=test`)) { url.pathname = "/test" + url.pathname; } else { // If there is no cookie, this is a new client. Choose a group and set the cookie. const group = Math.random() < 0.5 ? "test" : "control"; // 50/50 split if (group === "control") { url.pathname = "/control" + url.pathname; } else { url.pathname = "/test" + url.pathname; } // Reconstruct response to avoid immutability let res = await fetch(url); res = new Response(res.body, res); // Set cookie to enable persistent A/B sessions. res.headers.append("Set-Cookie", `${NAME}=${group}; path=/`); return res; } return fetch(url); }, } satisfies ExportedHandler; ``` Explain Code Python ``` import random from urllib.parse import urlparse, urlunparse from workers import Response, fetch, WorkerEntrypoint NAME = "myExampleWorkersABTest" class Default(WorkerEntrypoint): async def fetch(self, request): url = urlparse(request.url) # Uncomment below when testing locally # url = url._replace(netloc="example.com") if "localhost" in url.netloc else url # Enable Passthrough to allow direct access to control and test routes. if url.path.startswith("/control") or url.path.startswith("/test"): return fetch(urlunparse(url)) # Determine which group this requester is in. cookie = request.headers.get("cookie") if cookie and f'{NAME}=control' in cookie: url = url._replace(path="/control" + url.path) elif cookie and f'{NAME}=test' in cookie: url = url._replace(path="/test" + url.path) else: # If there is no cookie, this is a new client. Choose a group and set the cookie. group = "test" if random.random() < 0.5 else "control" if group == "control": url = url._replace(path="/control" + url.path) else: url = url._replace(path="/test" + url.path) # Reconstruct response to avoid immutability res = await fetch(urlunparse(url)) headers = dict(res.headers) headers["Set-Cookie"] = f'{NAME}={group}; path=/' return Response(res.body, headers=headers) return fetch(urlunparse(url)) ``` Explain Code TypeScript ``` import { Hono } from "hono"; import { getCookie, setCookie } from "hono/cookie"; const app = new Hono(); const NAME = "myExampleWorkersABTest"; // Enable passthrough to allow direct access to control and test routes app.all("/control/*", (c) => fetch(c.req.raw)); app.all("/test/*", (c) => fetch(c.req.raw)); // Middleware to handle A/B testing logic app.use("*", async (c) => { const url = new URL(c.req.url); // Determine which group this requester is in const abTestCookie = getCookie(c, NAME); if (abTestCookie === "control") { // User is in control group url.pathname = "/control" + c.req.path; } else if (abTestCookie === "test") { // User is in test group url.pathname = "/test" + c.req.path; } else { // If there is no cookie, this is a new client // Choose a group and set the cookie (50/50 split) const group = Math.random() < 0.5 ? "test" : "control"; // Update URL path based on assigned group if (group === "control") { url.pathname = "/control" + c.req.path; } else { url.pathname = "/test" + c.req.path; } // Set cookie to enable persistent A/B sessions setCookie(c, NAME, group, { path: "/", }); } const res = await fetch(url); return c.body(res.body, res); }); export default app; ``` Explain Code ```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/ab-testing/","name":"A/B testing with same-URL direct access"}}]} ``` --- --- title: Accessing the Cloudflare Object description: Access custom Cloudflare properties and control how Cloudflare features are applied to every request. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) ### Tags [ JavaScript ](https://developers.cloudflare.com/search/?tags=JavaScript)[ TypeScript ](https://developers.cloudflare.com/search/?tags=TypeScript)[ Python ](https://developers.cloudflare.com/search/?tags=Python) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/examples/accessing-the-cloudflare-object.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Accessing the Cloudflare Object **Last reviewed:** about 4 years ago Access custom Cloudflare properties and control how Cloudflare features are applied to every request. 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/accessing-the-cloudflare-object) This creates a repository in your GitHub account and deploys the application to Cloudflare Workers. * [ JavaScript ](#tab-panel-7353) * [ TypeScript ](#tab-panel-7354) * [ Hono ](#tab-panel-7355) * [ Python ](#tab-panel-7356) JavaScript ``` export default { async fetch(req) { const data = req.cf !== undefined ? req.cf : { error: "The `cf` object is not available inside the preview." }; return new Response(JSON.stringify(data, null, 2), { headers: { "content-type": "application/json;charset=UTF-8", }, }); }, }; ``` Explain Code TypeScript ``` export default { async fetch(req): Promise { const data = req.cf !== undefined ? req.cf : { error: "The `cf` object is not available inside the preview." }; return new Response(JSON.stringify(data, null, 2), { headers: { "content-type": "application/json;charset=UTF-8", }, }); }, } satisfies ExportedHandler; ``` Explain Code TypeScript ``` import { Hono } from "hono"; const app = new Hono(); app.get("*", async (c) => { // Access the raw request to get the cf object const req = c.req.raw; // Check if the cf object is available const data = req.cf !== undefined ? req.cf : { error: "The `cf` object is not available inside the preview." }; // Return the data formatted with 2-space indentation return c.json(data); }); export default app; ``` Explain Code Python ``` import json from workers import Response, WorkerEntrypoint from js import JSON class Default(WorkerEntrypoint): async def fetch(self, request): error = json.dumps({ "error": "The `cf` object is not available inside the preview." }) data = request.cf if request.cf is not None else error headers = {"content-type":"application/json"} return Response(JSON.stringify(data, None, 2), headers=headers) ``` Explain Code ```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/accessing-the-cloudflare-object/","name":"Accessing the Cloudflare Object"}}]} ``` --- --- title: Aggregate requests description: Send two GET request to two urls and aggregates the responses into one response. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) ### Tags [ JavaScript ](https://developers.cloudflare.com/search/?tags=JavaScript)[ TypeScript ](https://developers.cloudflare.com/search/?tags=TypeScript)[ Python ](https://developers.cloudflare.com/search/?tags=Python) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/examples/aggregate-requests.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Aggregate requests **Last reviewed:** about 4 years ago Send two GET request to two urls and aggregates the responses into one 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/aggregate-requests) This creates a repository in your GitHub account and deploys the application to Cloudflare Workers. * [ JavaScript ](#tab-panel-7357) * [ TypeScript ](#tab-panel-7358) * [ Hono ](#tab-panel-7359) * [ Python ](#tab-panel-7360) JavaScript ``` export default { async fetch(request) { // someHost is set up to return JSON responses const someHost = "https://jsonplaceholder.typicode.com"; const url1 = someHost + "/todos/1"; const url2 = someHost + "/todos/2"; const responses = await Promise.all([fetch(url1), fetch(url2)]); const results = await Promise.all(responses.map((r) => r.json())); const options = { headers: { "content-type": "application/json;charset=UTF-8" }, }; return new Response(JSON.stringify(results), options); }, }; ``` Explain Code TypeScript ``` export default { async fetch(request) { // someHost is set up to return JSON responses const someHost = "https://jsonplaceholder.typicode.com"; const url1 = someHost + "/todos/1"; const url2 = someHost + "/todos/2"; const responses = await Promise.all([fetch(url1), fetch(url2)]); const results = await Promise.all(responses.map((r) => r.json())); const options = { headers: { "content-type": "application/json;charset=UTF-8" }, }; return new Response(JSON.stringify(results), options); }, } satisfies ExportedHandler; ``` Explain Code TypeScript ``` import { Hono } from "hono"; const app = new Hono(); app.get("*", async (c) => { // someHost is set up to return JSON responses const someHost = "https://jsonplaceholder.typicode.com"; const url1 = someHost + "/todos/1"; const url2 = someHost + "/todos/2"; // Fetch both URLs concurrently const responses = await Promise.all([fetch(url1), fetch(url2)]); // Parse JSON responses concurrently const results = await Promise.all(responses.map((r) => r.json())); // Return aggregated results return c.json(results); }); export default app; ``` Explain Code Python ``` from workers import Response, fetch, WorkerEntrypoint import asyncio import json class Default(WorkerEntrypoint): async def fetch(self, request): # some_host is set up to return JSON responses some_host = "https://jsonplaceholder.typicode.com" url1 = some_host + "/todos/1" url2 = some_host + "/todos/2" responses = await asyncio.gather(fetch(url1), fetch(url2)) results = await asyncio.gather(*(r.json() for r in responses)) headers = {"content-type": "application/json;charset=UTF-8"} return Response.json(results, headers=headers) ``` Explain Code ```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/aggregate-requests/","name":"Aggregate requests"}}]} ``` --- --- title: Alter headers description: Example of how to add, change, or delete headers sent in a request or returned in a response. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) ### Tags [ Headers ](https://developers.cloudflare.com/search/?tags=Headers)[ Middleware ](https://developers.cloudflare.com/search/?tags=Middleware)[ JavaScript ](https://developers.cloudflare.com/search/?tags=JavaScript)[ TypeScript ](https://developers.cloudflare.com/search/?tags=TypeScript)[ Python ](https://developers.cloudflare.com/search/?tags=Python) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/examples/alter-headers.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Alter headers **Last reviewed:** over 5 years ago Example of how to add, change, or delete headers sent in a request or returned in a 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/alter-headers) This creates a repository in your GitHub account and deploys the application to Cloudflare Workers. * [ JavaScript ](#tab-panel-7361) * [ TypeScript ](#tab-panel-7362) * [ Python ](#tab-panel-7363) * [ Hono ](#tab-panel-7364) JavaScript ``` export default { async fetch(request) { const response = await fetch("https://example.com"); // Clone the response so that it's no longer immutable const newResponse = new Response(response.body, response); // Add a custom header with a value newResponse.headers.append( "x-workers-hello", "Hello from Cloudflare Workers", ); // Delete headers newResponse.headers.delete("x-header-to-delete"); newResponse.headers.delete("x-header2-to-delete"); // Adjust the value for an existing header newResponse.headers.set("x-header-to-change", "NewValue"); return newResponse; }, }; ``` Explain Code TypeScript ``` export default { async fetch(request): Promise { const response = await fetch(request); // Clone the response so that it's no longer immutable const newResponse = new Response(response.body, response); // Add a custom header with a value newResponse.headers.append( "x-workers-hello", "Hello from Cloudflare Workers", ); // Delete headers newResponse.headers.delete("x-header-to-delete"); newResponse.headers.delete("x-header2-to-delete"); // Adjust the value for an existing header newResponse.headers.set("x-header-to-change", "NewValue"); return newResponse; }, } satisfies ExportedHandler; ``` Explain Code Python ``` from workers import Response, fetch, WorkerEntrypoint class Default(WorkerEntrypoint): async def fetch(self, request): response = await fetch("https://example.com") # Grab the response headers so they can be modified new_headers = response.headers # Add a custom header with a value new_headers["x-workers-hello"] = "Hello from Cloudflare Workers" # Delete headers if "x-header-to-delete" in new_headers: del new_headers["x-header-to-delete"] if "x-header2-to-delete" in new_headers: del new_headers["x-header2-to-delete"] # Adjust the value for an existing header new_headers["x-header-to-change"] = "NewValue" return Response(response.body, headers=new_headers) ``` Explain Code TypeScript ``` import { Hono } from 'hono'; const app = new Hono(); app.use('*', async (c, next) => { // Process the request with the next middleware/handler await next(); // After the response is generated, we can modify its headers // Add a custom header with a value c.res.headers.append( "x-workers-hello", "Hello from Cloudflare Workers with Hono" ); // Delete headers c.res.headers.delete("x-header-to-delete"); c.res.headers.delete("x-header2-to-delete"); // Adjust the value for an existing header c.res.headers.set("x-header-to-change", "NewValue"); }); app.get('*', async (c) => { // Fetch content from example.com const response = await fetch("https://example.com"); // Return the response body with original headers // (our middleware will modify the headers before sending) return new Response(response.body, { headers: response.headers }); }); export default app; ``` Explain Code You can also use the [custom-headers-example template ↗](https://github.com/kristianfreeman/custom-headers-example) to deploy this code to your custom domain. ```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/alter-headers/","name":"Alter headers"}}]} ``` --- --- title: Write to Analytics Engine description: Write custom analytics events to Workers Analytics Engine for high-cardinality, time-series data. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/examples/analytics-engine.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Write to Analytics Engine **Last reviewed:** 3 months ago Write custom analytics events to Workers Analytics Engine. [Workers Analytics Engine](https://developers.cloudflare.com/analytics/analytics-engine/) provides time-series analytics at scale. Use it to track custom metrics, build usage-based billing, or understand service health on a per-customer basis. Unlike logs, Analytics Engine is designed for aggregated queries over high-cardinality data. Writes are non-blocking and do not impact request latency. ## Configure the binding Add an Analytics Engine dataset binding to your Wrangler configuration file. The dataset is created automatically when you first write to it. * [ wrangler.jsonc ](#tab-panel-7365) * [ wrangler.toml ](#tab-panel-7366) JSONC ``` { "analytics_engine_datasets": [ { "binding": "ANALYTICS", "dataset": "my_dataset", }, ], } ``` TOML ``` [[analytics_engine_datasets]] binding = "ANALYTICS" dataset = "my_dataset" ``` ## Write data points * [ JavaScript ](#tab-panel-7367) * [ TypeScript ](#tab-panel-7368) JavaScript ``` export default { async fetch(request, env) { const url = new URL(request.url); // Write a page view event env.ANALYTICS.writeDataPoint({ blobs: [ url.pathname, request.headers.get("cf-connecting-country") ?? "unknown", ], doubles: [1], // Count indexes: [url.hostname], // Sampling key }); // Write a response timing event const start = Date.now(); const response = await fetch(request); const duration = Date.now() - start; env.ANALYTICS.writeDataPoint({ blobs: [url.pathname, response.status.toString()], doubles: [duration], indexes: [url.hostname], }); // Writes are non-blocking - no need to await or use waitUntil() return response; }, }; ``` Explain Code TypeScript ``` interface Env { ANALYTICS: AnalyticsEngineDataset; } export default { async fetch(request: Request, env: Env): Promise { const url = new URL(request.url); // Write a page view event env.ANALYTICS.writeDataPoint({ blobs: [ url.pathname, request.headers.get("cf-connecting-country") ?? "unknown", ], doubles: [1], // Count indexes: [url.hostname], // Sampling key }); // Write a response timing event const start = Date.now(); const response = await fetch(request); const duration = Date.now() - start; env.ANALYTICS.writeDataPoint({ blobs: [url.pathname, response.status.toString()], doubles: [duration], indexes: [url.hostname], }); // Writes are non-blocking - no need to await or use waitUntil() return response; }, }; ``` Explain Code ## Data point structure Each data point consists of: * **blobs** (strings) - Dimensions for grouping and filtering. Use for paths, regions, status codes, or customer IDs. * **doubles** (numbers) - Numeric values to record, such as counts, durations, or sizes. * **indexes** (strings) - A single string used as the [sampling key](https://developers.cloudflare.com/analytics/analytics-engine/sql-api/#sampling). Group related events under the same index. ## Query your data Query your data using the [SQL API](https://developers.cloudflare.com/analytics/analytics-engine/sql-api/): Terminal window ``` curl "https://api.cloudflare.com/client/v4/accounts/$CLOUDFLARE_ACCOUNT_ID/analytics_engine/sql" \ --header "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \ --data "SELECT blob1 AS path, SUM(_sample_interval) AS views FROM my_dataset WHERE timestamp > NOW() - INTERVAL '1' HOUR GROUP BY path ORDER BY views DESC LIMIT 10" ``` ## Related resources * [Analytics Engine documentation](https://developers.cloudflare.com/analytics/analytics-engine/) \- Full reference for Workers Analytics Engine. * [SQL API reference](https://developers.cloudflare.com/analytics/analytics-engine/sql-api/) \- Query syntax and available functions. * [Grafana integration](https://developers.cloudflare.com/analytics/analytics-engine/grafana/) \- Visualize Analytics Engine data in Grafana. ```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/analytics-engine/","name":"Write to Analytics Engine"}}]} ``` --- --- title: Auth with headers description: Allow or deny a request based on a known pre-shared key in a header. This is not meant to replace the WebCrypto API. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) ### Tags [ Authentication ](https://developers.cloudflare.com/search/?tags=Authentication)[ Web Crypto ](https://developers.cloudflare.com/search/?tags=Web%20Crypto)[ JavaScript ](https://developers.cloudflare.com/search/?tags=JavaScript)[ TypeScript ](https://developers.cloudflare.com/search/?tags=TypeScript)[ Python ](https://developers.cloudflare.com/search/?tags=Python) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/examples/auth-with-headers.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Auth with headers **Last reviewed:** over 5 years ago Allow or deny a request based on a known pre-shared key in a header. This is not meant to replace the WebCrypto API. Caution when using in production The example code contains a generic header key and value of `X-Custom-PSK` and `mypresharedkey`. To best protect your resources, change the header key and value in the Workers editor before saving your code. * [ JavaScript ](#tab-panel-7369) * [ TypeScript ](#tab-panel-7370) * [ Python ](#tab-panel-7371) * [ Hono ](#tab-panel-7372) JavaScript ``` export default { async fetch(request) { /** * @param {string} PRESHARED_AUTH_HEADER_KEY Custom header to check for key * @param {string} PRESHARED_AUTH_HEADER_VALUE Hard coded key value */ const PRESHARED_AUTH_HEADER_KEY = "X-Custom-PSK"; const PRESHARED_AUTH_HEADER_VALUE = "mypresharedkey"; const psk = request.headers.get(PRESHARED_AUTH_HEADER_KEY); if (psk === PRESHARED_AUTH_HEADER_VALUE) { // Correct preshared header key supplied. Fetch request from origin. return fetch(request); } // Incorrect key supplied. Reject the request. return new Response("Sorry, you have supplied an invalid key.", { status: 403, }); }, }; ``` Explain Code TypeScript ``` export default { async fetch(request): Promise { /** * @param {string} PRESHARED_AUTH_HEADER_KEY Custom header to check for key * @param {string} PRESHARED_AUTH_HEADER_VALUE Hard coded key value */ const PRESHARED_AUTH_HEADER_KEY = "X-Custom-PSK"; const PRESHARED_AUTH_HEADER_VALUE = "mypresharedkey"; const psk = request.headers.get(PRESHARED_AUTH_HEADER_KEY); if (psk === PRESHARED_AUTH_HEADER_VALUE) { // Correct preshared header key supplied. Fetch request from origin. return fetch(request); } // Incorrect key supplied. Reject the request. return new Response("Sorry, you have supplied an invalid key.", { status: 403, }); }, } satisfies ExportedHandler; ``` Explain Code Python ``` from workers import WorkerEntrypoint, Response, fetch class Default(WorkerEntrypoint): async def fetch(self, request): PRESHARED_AUTH_HEADER_KEY = "X-Custom-PSK" PRESHARED_AUTH_HEADER_VALUE = "mypresharedkey" psk = request.headers[PRESHARED_AUTH_HEADER_KEY] if psk == PRESHARED_AUTH_HEADER_VALUE: # Correct preshared header key supplied. Fetch request from origin. return fetch(request) # Incorrect key supplied. Reject the request. return Response("Sorry, you have supplied an invalid key.", status=403) ``` Explain Code TypeScript ``` import { Hono } from 'hono'; const app = new Hono(); // Add authentication middleware app.use('*', async (c, next) => { /** * Define authentication constants */ const PRESHARED_AUTH_HEADER_KEY = "X-Custom-PSK"; const PRESHARED_AUTH_HEADER_VALUE = "mypresharedkey"; // Get the pre-shared key from the request header const psk = c.req.header(PRESHARED_AUTH_HEADER_KEY); if (psk === PRESHARED_AUTH_HEADER_VALUE) { // Correct preshared header key supplied. Continue to the next handler. await next(); } else { // Incorrect key supplied. Reject the request. return c.text("Sorry, you have supplied an invalid key.", 403); } }); // Handle all authenticated requests by passing through to origin app.all('*', async (c) => { return fetch(c.req.raw); }); export default app; ``` Explain Code ```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/auth-with-headers/","name":"Auth with headers"}}]} ``` --- --- title: HTTP Basic Authentication description: Shows how to restrict access using the HTTP Basic schema. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) ### Tags [ Security ](https://developers.cloudflare.com/search/?tags=Security)[ Authentication ](https://developers.cloudflare.com/search/?tags=Authentication)[ JavaScript ](https://developers.cloudflare.com/search/?tags=JavaScript)[ TypeScript ](https://developers.cloudflare.com/search/?tags=TypeScript)[ Rust ](https://developers.cloudflare.com/search/?tags=Rust) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/examples/basic-auth.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # HTTP Basic Authentication **Last reviewed:** over 2 years ago Shows how to restrict access using the HTTP Basic schema. Note This example Worker makes use of the [Node.js Buffer API](https://developers.cloudflare.com/workers/runtime-apis/nodejs/buffer/), which is available as part of the Workers runtime [Node.js compatibility mode](https://developers.cloudflare.com/workers/runtime-apis/nodejs/). To run this Worker, you will need to [enable the nodejs\_compat compatibility flag](https://developers.cloudflare.com/workers/configuration/compatibility-flags/#nodejs-compatibility-flag). Caution when using in production This code is provided as a sample, and is not suitable for production use. Basic Authentication sends credentials unencrypted, and must be used with an HTTPS connection to be considered secure. For a production-ready authentication system, consider using [Cloudflare Access ↗](https://developers.cloudflare.com/cloudflare-one/access-controls/applications/http-apps/self-hosted-public-app/). * [ JavaScript ](#tab-panel-7373) * [ TypeScript ](#tab-panel-7374) * [ Rust ](#tab-panel-7375) * [ Hono ](#tab-panel-7376) JavaScript ``` /** * Shows how to restrict access using the HTTP Basic schema. * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication * @see https://tools.ietf.org/html/rfc7617 * */ import { Buffer } from "node:buffer"; const encoder = new TextEncoder(); /** * Protect against timing attacks by safely comparing values using `timingSafeEqual`. * Refer to https://developers.cloudflare.com/workers/runtime-apis/web-crypto/#timingsafeequal for more details * @param {string} a * @param {string} b * @returns {boolean} */ function timingSafeEqual(a, b) { const aBytes = encoder.encode(a); const bBytes = encoder.encode(b); // Do not return early when lengths differ — that leaks the secret's // length through timing. Compare against self and negate instead. if (aBytes.byteLength !== bBytes.byteLength) { return !crypto.subtle.timingSafeEqual(aBytes, aBytes); } return crypto.subtle.timingSafeEqual(aBytes, bBytes); } export default { /** * * @param {Request} request * @param {{PASSWORD: string}} env * @returns */ async fetch(request, env) { const BASIC_USER = "admin"; // You will need an admin password. This should be // attached to your Worker as an encrypted secret. // Refer to https://developers.cloudflare.com/workers/configuration/secrets/ const BASIC_PASS = env.PASSWORD ?? "password"; const url = new URL(request.url); switch (url.pathname) { case "/": return new Response("Anyone can access the homepage."); case "/logout": // Invalidate the "Authorization" header by returning a HTTP 401. // We do not send a "WWW-Authenticate" header, as this would trigger // a popup in the browser, immediately asking for credentials again. return new Response("Logged out.", { status: 401 }); case "/admin": { // The "Authorization" header is sent when authenticated. const authorization = request.headers.get("Authorization"); if (!authorization) { return new Response("You need to login.", { status: 401, headers: { // Prompts the user for credentials. "WWW-Authenticate": 'Basic realm="my scope", charset="UTF-8"', }, }); } const [scheme, encoded] = authorization.split(" "); // The Authorization header must start with Basic, followed by a space. if (!encoded || scheme !== "Basic") { return new Response("Malformed authorization header.", { status: 400, }); } const credentials = Buffer.from(encoded, "base64").toString(); // The username & password are split by the first colon. //=> example: "username:password" const index = credentials.indexOf(":"); const user = credentials.substring(0, index); const pass = credentials.substring(index + 1); if ( !timingSafeEqual(BASIC_USER, user) || !timingSafeEqual(BASIC_PASS, pass) ) { return new Response("You need to login.", { status: 401, headers: { // Prompts the user for credentials. "WWW-Authenticate": 'Basic realm="my scope", charset="UTF-8"', }, }); } return new Response("🎉 You have private access!", { status: 200, headers: { "Cache-Control": "no-store", }, }); } } return new Response("Not Found.", { status: 404 }); }, }; ``` Explain Code TypeScript ``` /** * Shows how to restrict access using the HTTP Basic schema. * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication * @see https://tools.ietf.org/html/rfc7617 * */ import { Buffer } from "node:buffer"; const encoder = new TextEncoder(); /** * Protect against timing attacks by safely comparing values using `timingSafeEqual`. * Refer to https://developers.cloudflare.com/workers/runtime-apis/web-crypto/#timingsafeequal for more details */ function timingSafeEqual(a: string, b: string) { const aBytes = encoder.encode(a); const bBytes = encoder.encode(b); // Do not return early when lengths differ — that leaks the secret's // length through timing. Compare against self and negate instead. if (aBytes.byteLength !== bBytes.byteLength) { return !crypto.subtle.timingSafeEqual(aBytes, aBytes); } return crypto.subtle.timingSafeEqual(aBytes, bBytes); } interface Env { PASSWORD: string; } export default { async fetch(request, env): Promise { const BASIC_USER = "admin"; // You will need an admin password. This should be // attached to your Worker as an encrypted secret. // Refer to https://developers.cloudflare.com/workers/configuration/secrets/ const BASIC_PASS = env.PASSWORD ?? "password"; const url = new URL(request.url); switch (url.pathname) { case "/": return new Response("Anyone can access the homepage."); case "/logout": // Invalidate the "Authorization" header by returning a HTTP 401. // We do not send a "WWW-Authenticate" header, as this would trigger // a popup in the browser, immediately asking for credentials again. return new Response("Logged out.", { status: 401 }); case "/admin": { // The "Authorization" header is sent when authenticated. const authorization = request.headers.get("Authorization"); if (!authorization) { return new Response("You need to login.", { status: 401, headers: { // Prompts the user for credentials. "WWW-Authenticate": 'Basic realm="my scope", charset="UTF-8"', }, }); } const [scheme, encoded] = authorization.split(" "); // The Authorization header must start with Basic, followed by a space. if (!encoded || scheme !== "Basic") { return new Response("Malformed authorization header.", { status: 400, }); } const credentials = Buffer.from(encoded, "base64").toString(); // The username and password are split by the first colon. //=> example: "username:password" const index = credentials.indexOf(":"); const user = credentials.substring(0, index); const pass = credentials.substring(index + 1); if ( !timingSafeEqual(BASIC_USER, user) || !timingSafeEqual(BASIC_PASS, pass) ) { return new Response("You need to login.", { status: 401, headers: { // Prompts the user for credentials. "WWW-Authenticate": 'Basic realm="my scope", charset="UTF-8"', }, }); } return new Response("🎉 You have private access!", { status: 200, headers: { "Cache-Control": "no-store", }, }); } } return new Response("Not Found.", { status: 404 }); }, } satisfies ExportedHandler; ``` Explain Code ``` use base64::prelude::*; use worker::*; #[event(fetch)] async fn fetch(req: Request, env: Env, _ctx: Context) -> Result { let basic_user = "admin"; // You will need an admin password. This should be // attached to your Worker as an encrypted secret. // Refer to https://developers.cloudflare.com/workers/configuration/secrets/ let basic_pass = match env.secret("PASSWORD") { Ok(s) => s.to_string(), Err(_) => "password".to_string(), }; let url = req.url()?; match url.path() { "/" => Response::ok("Anyone can access the homepage."), // Invalidate the "Authorization" header by returning a HTTP 401. // We do not send a "WWW-Authenticate" header, as this would trigger // a popup in the browser, immediately asking for credentials again. "/logout" => Response::error("Logged out.", 401), "/admin" => { // The "Authorization" header is sent when authenticated. let authorization = req.headers().get("Authorization")?; if authorization == None { let mut headers = Headers::new(); // Prompts the user for credentials. headers.set( "WWW-Authenticate", "Basic realm='my scope', charset='UTF-8'", )?; return Ok(Response::error("You need to login.", 401)?.with_headers(headers)); } let authorization = authorization.unwrap(); let auth: Vec<&str> = authorization.split(" ").collect(); let scheme = auth[0]; let encoded = auth[1]; // The Authorization header must start with Basic, followed by a space. if encoded == "" || scheme != "Basic" { return Response::error("Malformed authorization header.", 400); } let buff = BASE64_STANDARD.decode(encoded).unwrap(); let credentials = String::from_utf8_lossy(&buff); // The username & password are split by the first colon. //=> example: "username:password" let credentials: Vec<&str> = credentials.split(':').collect(); let user = credentials[0]; let pass = credentials[1]; if user != basic_user || pass != basic_pass { let mut headers = Headers::new(); // Prompts the user for credentials. headers.set( "WWW-Authenticate", "Basic realm='my scope', charset='UTF-8'", )?; return Ok(Response::error("You need to login.", 401)?.with_headers(headers)); } let mut headers = Headers::new(); headers.set("Cache-Control", "no-store")?; Ok(Response::ok("🎉 You have private access!")?.with_headers(headers)) } _ => Response::error("Not Found.", 404), } } ``` Explain Code TypeScript ``` /** * Shows how to restrict access using the HTTP Basic schema with Hono. * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication * @see https://tools.ietf.org/html/rfc7617 */ import { Hono } from "hono"; import { basicAuth } from "hono/basic-auth"; // Define environment interface interface Env { Bindings: { USERNAME: string; PASSWORD: string; }; } const app = new Hono(); // Public homepage - accessible to everyone app.get("/", (c) => { return c.text("Anyone can access the homepage."); }); // Admin route - protected with Basic Auth app.get( "/admin", async (c, next) => { const auth = basicAuth({ username: c.env.USERNAME, password: c.env.PASSWORD, }); return await auth(c, next); }, (c) => { return c.text("🎉 You have private access!", 200, { "Cache-Control": "no-store", }); }, ); export default app; ``` Explain Code ```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/basic-auth/","name":"HTTP Basic Authentication"}}]} ``` --- --- title: Block on TLS description: Inspects the incoming request's TLS version and blocks if under TLSv1.2. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) ### Tags [ Security ](https://developers.cloudflare.com/search/?tags=Security)[ Middleware ](https://developers.cloudflare.com/search/?tags=Middleware)[ JavaScript ](https://developers.cloudflare.com/search/?tags=JavaScript)[ TypeScript ](https://developers.cloudflare.com/search/?tags=TypeScript)[ Python ](https://developers.cloudflare.com/search/?tags=Python) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/examples/block-on-tls.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Block on TLS **Last reviewed:** about 4 years ago Inspects the incoming request's TLS version and blocks if under TLSv1.2. * [ JavaScript ](#tab-panel-7377) * [ TypeScript ](#tab-panel-7378) * [ Hono ](#tab-panel-7379) * [ Python ](#tab-panel-7380) JavaScript ``` export default { async fetch(request) { try { const tlsVersion = request.cf.tlsVersion; // Allow only TLS versions 1.2 and 1.3 if (tlsVersion !== "TLSv1.2" && tlsVersion !== "TLSv1.3") { return new Response("Please use TLS version 1.2 or higher.", { status: 403, }); } return fetch(request); } catch (err) { console.error( "request.cf does not exist in the previewer, only in production", ); return new Response(`Error in workers script ${err.message}`, { status: 500, }); } }, }; ``` Explain Code TypeScript ``` export default { async fetch(request): Promise { try { const tlsVersion = request.cf.tlsVersion; // Allow only TLS versions 1.2 and 1.3 if (tlsVersion !== "TLSv1.2" && tlsVersion !== "TLSv1.3") { return new Response("Please use TLS version 1.2 or higher.", { status: 403, }); } return fetch(request); } catch (err) { console.error( "request.cf does not exist in the previewer, only in production", ); return new Response(`Error in workers script ${err.message}`, { status: 500, }); } }, } satisfies ExportedHandler; ``` Explain Code TypeScript ``` import { Hono } from "hono"; const app = new Hono(); // Middleware to check TLS version app.use("*", async (c, next) => { // Access the raw request to get the cf object with TLS info const request = c.req.raw; const tlsVersion = request.cf?.tlsVersion; // Allow only TLS versions 1.2 and 1.3 if (tlsVersion !== "TLSv1.2" && tlsVersion !== "TLSv1.3") { return c.text("Please use TLS version 1.2 or higher.", 403); } await next(); }); app.onError((err, c) => { console.error( "request.cf does not exist in the previewer, only in production", ); return c.text(`Error in workers script: ${err.message}`, 500); }); app.get("/", async (c) => { return c.text(`TLS Version: ${c.req.raw.cf.tlsVersion}`); }); export default app; ``` Explain Code Python ``` from workers import WorkerEntrypoint, Response, fetch class Default(WorkerEntrypoint): async def fetch(self, request): tls_version = request.cf.tlsVersion if tls_version not in ("TLSv1.2", "TLSv1.3"): return Response("Please use TLS version 1.2 or higher.", status=403) return fetch(request) ``` ```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/block-on-tls/","name":"Block on TLS"}}]} ``` --- --- title: Bulk origin override description: Resolve requests to your domain to a set of proxy third-party origin URLs. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) ### Tags [ Middleware ](https://developers.cloudflare.com/search/?tags=Middleware)[ JavaScript ](https://developers.cloudflare.com/search/?tags=JavaScript)[ TypeScript ](https://developers.cloudflare.com/search/?tags=TypeScript)[ Python ](https://developers.cloudflare.com/search/?tags=Python) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/examples/bulk-origin-proxy.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Bulk origin override **Last reviewed:** over 5 years ago Resolve requests to your domain to a set of proxy third-party origin URLs. * [ JavaScript ](#tab-panel-7381) * [ TypeScript ](#tab-panel-7382) * [ Hono ](#tab-panel-7383) * [ Python ](#tab-panel-7384) JavaScript ``` export default { async fetch(request) { /** * An object with different URLs to fetch * @param {Object} ORIGINS */ const ORIGINS = { "starwarsapi.yourdomain.com": "swapi.dev", "google.yourdomain.com": "www.google.com", }; const url = new URL(request.url); // Check if incoming hostname is a key in the ORIGINS object if (url.hostname in ORIGINS) { const target = ORIGINS[url.hostname]; url.hostname = target; // If it is, proxy request to that third party origin return fetch(url.toString(), request); } // Otherwise, process request as normal return fetch(request); }, }; ``` Explain Code TypeScript ``` export default { async fetch(request): Promise { /** * An object with different URLs to fetch * @param {Object} ORIGINS */ const ORIGINS = { "starwarsapi.yourdomain.com": "swapi.dev", "google.yourdomain.com": "www.google.com", }; const url = new URL(request.url); // Check if incoming hostname is a key in the ORIGINS object if (url.hostname in ORIGINS) { const target = ORIGINS[url.hostname]; url.hostname = target; // If it is, proxy request to that third party origin return fetch(url.toString(), request); } // Otherwise, process request as normal return fetch(request); }, } satisfies ExportedHandler; ``` Explain Code TypeScript ``` import { Hono } from "hono"; import { proxy } from "hono/proxy"; // An object with different URLs to fetch const ORIGINS: Record = { "starwarsapi.yourdomain.com": "swapi.dev", "google.yourdomain.com": "www.google.com", }; const app = new Hono(); app.all("*", async (c) => { const url = new URL(c.req.url); // Check if incoming hostname is a key in the ORIGINS object if (url.hostname in ORIGINS) { const target = ORIGINS[url.hostname]; url.hostname = target; // If it is, proxy request to that third party origin return proxy(url, c.req.raw); } // Otherwise, process request as normal return proxy(c.req.raw); }); export default app; ``` Explain Code Python ``` from workers import WorkerEntrypoint from js import fetch, URL class Default(WorkerEntrypoint): async def fetch(self, request): # A dict with different URLs to fetch ORIGINS = { "starwarsapi.yourdomain.com": "swapi.dev", "google.yourdomain.com": "www.google.com", } url = URL.new(request.url) # Check if incoming hostname is a key in the ORIGINS object if url.hostname in ORIGINS: url.hostname = ORIGINS[url.hostname] # If it is, proxy request to that third party origin return fetch(url.toString(), request) # Otherwise, process request as normal return fetch(request) ``` Explain Code ```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/bulk-origin-proxy/","name":"Bulk origin override"}}]} ``` --- --- title: Bulk redirects description: Redirect requests to certain URLs based on a mapped object to the request's URL. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) ### Tags [ Middleware ](https://developers.cloudflare.com/search/?tags=Middleware)[ Redirects ](https://developers.cloudflare.com/search/?tags=Redirects)[ JavaScript ](https://developers.cloudflare.com/search/?tags=JavaScript)[ TypeScript ](https://developers.cloudflare.com/search/?tags=TypeScript)[ Python ](https://developers.cloudflare.com/search/?tags=Python) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/examples/bulk-redirects.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Bulk redirects **Last reviewed:** about 4 years ago Redirect requests to certain URLs based on a mapped object to the request's URL. * [ JavaScript ](#tab-panel-7385) * [ TypeScript ](#tab-panel-7386) * [ Python ](#tab-panel-7387) * [ Hono ](#tab-panel-7388) JavaScript ``` export default { async fetch(request) { const externalHostname = "examples.cloudflareworkers.com"; const redirectMap = new Map([ ["/bulk1", "https://" + externalHostname + "/redirect2"], ["/bulk2", "https://" + externalHostname + "/redirect3"], ["/bulk3", "https://" + externalHostname + "/redirect4"], ["/bulk4", "https://google.com"], ]); const requestURL = new URL(request.url); const path = requestURL.pathname; const location = redirectMap.get(path); if (location) { return Response.redirect(location, 301); } // If request not in map, return the original request return fetch(request); }, }; ``` Explain Code TypeScript ``` export default { async fetch(request): Promise { const externalHostname = "examples.cloudflareworkers.com"; const redirectMap = new Map([ ["/bulk1", "https://" + externalHostname + "/redirect2"], ["/bulk2", "https://" + externalHostname + "/redirect3"], ["/bulk3", "https://" + externalHostname + "/redirect4"], ["/bulk4", "https://google.com"], ]); const requestURL = new URL(request.url); const path = requestURL.pathname; const location = redirectMap.get(path); if (location) { return Response.redirect(location, 301); } // If request not in map, return the original request return fetch(request); }, } satisfies ExportedHandler; ``` Explain Code Python ``` from workers import WorkerEntrypoint, Response, fetch from urllib.parse import urlparse class Default(WorkerEntrypoint): async def fetch(self, request): external_hostname = "examples.cloudflareworkers.com" redirect_map = { "/bulk1": "https://" + external_hostname + "/redirect2", "/bulk2": "https://" + external_hostname + "/redirect3", "/bulk3": "https://" + external_hostname + "/redirect4", "/bulk4": "https://google.com", } url = urlparse(request.url) location = redirect_map.get(url.path, None) if location: return Response.redirect(location, 301) # If request not in map, return the original request return fetch(request) ``` Explain Code TypeScript ``` import { Hono } from "hono"; const app = new Hono(); // Configure your redirects const externalHostname = "examples.cloudflareworkers.com"; const redirectMap = new Map([ ["/bulk1", `https://${externalHostname}/redirect2`], ["/bulk2", `https://${externalHostname}/redirect3`], ["/bulk3", `https://${externalHostname}/redirect4`], ["/bulk4", "https://google.com"], ]); // Middleware to handle redirects app.use("*", async (c, next) => { const path = c.req.path; const location = redirectMap.get(path); if (location) { // If path is in our redirect map, perform the redirect return c.redirect(location, 301); } // Otherwise, continue to the next handler await next(); }); // Default handler for requests that don't match any redirects app.all("*", async (c) => { // Pass through to origin return fetch(c.req.raw); }); export default app; ``` Explain Code ```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/bulk-redirects/","name":"Bulk redirects"}}]} ``` --- --- title: Using the Cache API description: Use the Cache API to store responses in Cloudflare's cache. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) ### Tags [ Middleware ](https://developers.cloudflare.com/search/?tags=Middleware)[ Caching ](https://developers.cloudflare.com/search/?tags=Caching)[ JavaScript ](https://developers.cloudflare.com/search/?tags=JavaScript)[ TypeScript ](https://developers.cloudflare.com/search/?tags=TypeScript)[ Python ](https://developers.cloudflare.com/search/?tags=Python) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/examples/cache-api.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Using the Cache API **Last reviewed:** over 5 years ago Use the Cache API to store responses in Cloudflare's cache. 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/cache-api) This creates a repository in your GitHub account and deploys the application to Cloudflare Workers. * [ JavaScript ](#tab-panel-7389) * [ TypeScript ](#tab-panel-7390) * [ Python ](#tab-panel-7391) * [ Hono ](#tab-panel-7392) JavaScript ``` export default { async fetch(request, env, ctx) { const cacheUrl = new URL(request.url); // Construct the cache key from the cache URL const cacheKey = new Request(cacheUrl.toString(), request); const cache = caches.default; // Check whether the value is already available in the cache // if not, you will need to fetch it from origin, and store it in the cache let response = await cache.match(cacheKey); if (!response) { console.log( `Response for request url: ${request.url} not present in cache. Fetching and caching request.`, ); // 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 s-maxage to 10 // will limit the response to be in cache for 10 seconds max // Any changes made to the response here will be reflected in the cached value response.headers.append("Cache-Control", "s-maxage=10"); ctx.waitUntil(cache.put(cacheKey, response.clone())); } else { console.log(`Cache hit for: ${request.url}.`); } return response; }, }; ``` Explain Code TypeScript ``` interface Env {} export default { async fetch(request, env, ctx): Promise { const cacheUrl = new URL(request.url); // Construct the cache key from the cache URL const cacheKey = new Request(cacheUrl.toString(), request); const cache = caches.default; // Check whether the value is already available in the cache // if not, you will need to fetch it from origin, and store it in the cache let response = await cache.match(cacheKey); if (!response) { console.log( `Response for request url: ${request.url} not present in cache. Fetching and caching request.`, ); // 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 s-maxage to 10 // will limit the response to be in cache for 10 seconds max // Any changes made to the response here will be reflected in the cached value response.headers.append("Cache-Control", "s-maxage=10"); ctx.waitUntil(cache.put(cacheKey, response.clone())); } else { console.log(`Cache hit for: ${request.url}.`); } return response; }, } satisfies ExportedHandler; ``` Explain Code Python ``` from workers import WorkerEntrypoint from pyodide.ffi import create_proxy from js import Response, Request, URL, caches, fetch class Default(WorkerEntrypoint): async def fetch(self, request): cache_url = request.url # Construct the cache key from the cache URL cache_key = Request.new(cache_url, request) cache = caches.default # Check whether the value is already available in the cache # if not, you will need to fetch it from origin, and store it in the cache response = await cache.match(cache_key) if response is None: print(f"Response for request url: {request.url} not present in cache. Fetching and caching request.") # If not in cache, get it from origin response = await fetch(request) # Must use Response constructor to inherit all of response's fields response = Response.new(response.body, response) # Cache API respects Cache-Control headers. Setting s-max-age to 10 # will limit the response to be in cache for 10 seconds s-maxage # Any changes made to the response here will be reflected in the cached value response.headers.append("Cache-Control", "s-maxage=10") self.ctx.waitUntil(create_proxy(cache.put(cache_key, response.clone()))) else: print(f"Cache hit for: {request.url}.") return response ``` Explain Code TypeScript ``` import { Hono } from "hono"; import { cache } from "hono/cache"; const app = new Hono(); // We leverage hono built-in cache helper here app.get( "*", cache({ cacheName: "my-cache", cacheControl: "max-age=3600", // 1 hour }), ); // Add a route to handle the request if it's not in cache app.get("*", (c) => { return c.text("Hello from Hono!"); }); export default app; ``` Explain Code ```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/cache-api/","name":"Using the Cache API"}}]} ``` --- --- title: Cache POST requests description: Cache POST requests using the Cache API. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) ### Tags [ Middleware ](https://developers.cloudflare.com/search/?tags=Middleware)[ Caching ](https://developers.cloudflare.com/search/?tags=Caching)[ JavaScript ](https://developers.cloudflare.com/search/?tags=JavaScript)[ TypeScript ](https://developers.cloudflare.com/search/?tags=TypeScript)[ Python ](https://developers.cloudflare.com/search/?tags=Python) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/examples/cache-post-request.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Cache POST requests **Last reviewed:** about 4 years ago Cache POST requests using the Cache API. 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/cache-post-request) This creates a repository in your GitHub account and deploys the application to Cloudflare Workers. * [ JavaScript ](#tab-panel-7393) * [ TypeScript ](#tab-panel-7394) * [ Python ](#tab-panel-7395) * [ Hono ](#tab-panel-7396) JavaScript ``` export default { async fetch(request, env, ctx) { async function sha256(message) { // encode as UTF-8 const msgBuffer = await new TextEncoder().encode(message); // hash the message const hashBuffer = await crypto.subtle.digest("SHA-256", msgBuffer); // convert bytes to hex string return [...new Uint8Array(hashBuffer)] .map((b) => b.toString(16).padStart(2, "0")) .join(""); } try { if (request.method.toUpperCase() === "POST") { const body = await request.clone().text(); // Hash the request body to use it as a part of the cache key 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); ctx.waitUntil(cache.put(cacheKey, response.clone())); } return response; } return fetch(request); } catch (e) { return new Response("Error thrown " + e.message); } }, }; ``` Explain Code TypeScript ``` interface Env {} export default { async fetch(request, env, ctx): Promise { async function sha256(message) { // encode as UTF-8 const msgBuffer = await new TextEncoder().encode(message); // hash the message const hashBuffer = await crypto.subtle.digest("SHA-256", msgBuffer); // convert bytes to hex string return [...new Uint8Array(hashBuffer)] .map((b) => b.toString(16).padStart(2, "0")) .join(""); } try { if (request.method.toUpperCase() === "POST") { const body = await request.clone().text(); // Hash the request body to use it as a part of the cache key 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); ctx.waitUntil(cache.put(cacheKey, response.clone())); } return response; } return fetch(request); } catch (e) { return new Response("Error thrown " + e.message); } }, } satisfies ExportedHandler; ``` Explain Code Python ``` import hashlib from workers import WorkerEntrypoint from pyodide.ffi import create_proxy from js import fetch, URL, Headers, Request, caches class Default(WorkerEntrypoint): async def fetch(self, request, _, ctx): if 'POST' in request.method: # Hash the request body to use it as a part of the cache key body = await request.clone().text() body_hash = hashlib.sha256(body.encode('UTF-8')).hexdigest() # Store the URL in cache by prepending the body's hash cache_url = URL.new(request.url) cache_url.pathname = "/posts" + cache_url.pathname + body_hash # Convert to a GET to be able to cache headers = Headers.new(dict(request.headers).items()) cache_key = Request.new(cache_url.toString(), method='GET', headers=headers) # Find the cache key in the cache cache = caches.default response = await cache.match(cache_key) # Otherwise, fetch response to POST request from origin if response is None: response = await fetch(request) ctx.waitUntil(create_proxy(cache.put(cache_key, response.clone()))) return response return fetch(request) ``` Explain Code TypeScript ``` import { Hono } from "hono"; import { sha256 } from "hono/utils/crypto"; const app = new Hono(); // Middleware for caching POST requests app.post("*", async (c) => { try { // Get the request body const body = await c.req.raw.clone().text(); // Hash the request body to use it as part of the cache key const hash = await sha256(body); // Create the cache URL const cacheUrl = new URL(c.req.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: c.req.raw.headers, method: "GET", }); const cache = caches.default; // Find the cache key in the cache let response = await cache.match(cacheKey); // If not in cache, fetch response to POST request from origin if (!response) { response = await fetch(c.req.raw); c.executionCtx.waitUntil(cache.put(cacheKey, response.clone())); } return response; } catch (e) { return c.text("Error thrown " + e.message, 500); } }); // Handle all other HTTP methods app.all("*", (c) => { return fetch(c.req.raw); }); export default app; ``` Explain Code ```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/cache-post-request/","name":"Cache POST requests"}}]} ``` --- --- title: Cache Tags using Workers description: Send Additional Cache Tags using Workers image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) ### Tags [ Caching ](https://developers.cloudflare.com/search/?tags=Caching)[ JavaScript ](https://developers.cloudflare.com/search/?tags=JavaScript)[ TypeScript ](https://developers.cloudflare.com/search/?tags=TypeScript)[ Python ](https://developers.cloudflare.com/search/?tags=Python) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/examples/cache-tags.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Cache Tags using Workers **Last reviewed:** almost 4 years ago Send Additional Cache Tags using Workers 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/cache-tags) This creates a repository in your GitHub account and deploys the application to Cloudflare Workers. * [ JavaScript ](#tab-panel-7397) * [ TypeScript ](#tab-panel-7398) * [ Hono ](#tab-panel-7399) * [ Python ](#tab-panel-7400) JavaScript ``` export default { async fetch(request) { const requestUrl = new URL(request.url); const params = requestUrl.searchParams; const tags = params && params.has("tags") ? params.get("tags").split(",") : []; const url = params && params.has("uri") ? params.get("uri") : ""; if (!url) { const errorObject = { error: "URL cannot be empty", }; return new Response(JSON.stringify(errorObject), { status: 400 }); } const init = { cf: { cacheTags: tags, }, }; return fetch(url, init) .then((result) => { const cacheStatus = result.headers.get("cf-cache-status"); const lastModified = result.headers.get("last-modified"); const response = { cache: cacheStatus, lastModified: lastModified, }; return new Response(JSON.stringify(response), { status: result.status, }); }) .catch((err) => { const errorObject = { error: err.message, }; return new Response(JSON.stringify(errorObject), { status: 500 }); }); }, }; ``` Explain Code TypeScript ``` export default { async fetch(request): Promise { const requestUrl = new URL(request.url); const params = requestUrl.searchParams; const tags = params && params.has("tags") ? params.get("tags").split(",") : []; const url = params && params.has("uri") ? params.get("uri") : ""; if (!url) { const errorObject = { error: "URL cannot be empty", }; return new Response(JSON.stringify(errorObject), { status: 400 }); } const init = { cf: { cacheTags: tags, }, }; return fetch(url, init) .then((result) => { const cacheStatus = result.headers.get("cf-cache-status"); const lastModified = result.headers.get("last-modified"); const response = { cache: cacheStatus, lastModified: lastModified, }; return new Response(JSON.stringify(response), { status: result.status, }); }) .catch((err) => { const errorObject = { error: err.message, }; return new Response(JSON.stringify(errorObject), { status: 500 }); }); }, } satisfies ExportedHandler; ``` Explain Code TypeScript ``` import { Hono } from "hono"; const app = new Hono(); app.all("*", async (c) => { const tags = c.req.query("tags") ? c.req.query("tags").split(",") : []; const uri = c.req.query("uri") ? c.req.query("uri") : ""; if (!uri) { return c.json({ error: "URL cannot be empty" }, 400); } const init = { cf: { cacheTags: tags, }, }; const result = await fetch(uri, init); const cacheStatus = result.headers.get("cf-cache-status"); const lastModified = result.headers.get("last-modified"); const response = { cache: cacheStatus, lastModified: lastModified, }; return c.json(response, result.status); }); app.onError((err, c) => { return c.json({ error: err.message }, 500); }); export default app; ``` Explain Code Python ``` from workers import WorkerEntrypoint from pyodide.ffi import to_js as _to_js from js import Response, URL, Object, fetch def to_js(x): return _to_js(x, dict_converter=Object.fromEntries) class Default(WorkerEntrypoint): async def fetch(self, request): request_url = URL.new(request.url) params = request_url.searchParams tags = params["tags"].split(",") if "tags" in params else [] url = params["uri"] or None if url is None: error = {"error": "URL cannot be empty"} return Response.json(to_js(error), status=400) options = {"cf": {"cacheTags": tags}} result = await fetch(url, to_js(options)) cache_status = result.headers["cf-cache-status"] last_modified = result.headers["last-modified"] response = {"cache": cache_status, "lastModified": last_modified} return Response.json(to_js(response), status=result.status) ``` Explain Code ```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/cache-tags/","name":"Cache Tags using Workers"}}]} ``` --- --- title: Cache using fetch description: Determine how to cache a resource by setting TTLs, custom cache keys, and cache headers in a fetch request. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) ### Tags [ Caching ](https://developers.cloudflare.com/search/?tags=Caching)[ Middleware ](https://developers.cloudflare.com/search/?tags=Middleware)[ 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) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/examples/cache-using-fetch.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Cache using fetch **Last reviewed:** over 5 years ago Determine how to cache a resource by setting TTLs, custom cache keys, and cache headers in a fetch request. 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/cache-using-fetch) This creates a repository in your GitHub account and deploys the application to Cloudflare Workers. * [ JavaScript ](#tab-panel-7401) * [ TypeScript ](#tab-panel-7402) * [ Hono ](#tab-panel-7403) * [ Python ](#tab-panel-7404) * [ Rust ](#tab-panel-7405) JavaScript ``` export default { async fetch(request) { const url = new URL(request.url); // Only use the path for the cache key, removing query strings // and always store using HTTPS, for example, https://www.example.com/file-uri-here const someCustomKey = `https://${url.hostname}${url.pathname}`; let response = await fetch(request, { cf: { // Always cache this fetch regardless of content type // for a max of 5 seconds before revalidating the resource cacheTtl: 5, cacheEverything: true, //Enterprise only feature, see Cache API for other plans cacheKey: someCustomKey, }, }); // Reconstruct the Response object to make its headers mutable. response = new Response(response.body, response); // Set cache control headers to cache on browser for 25 minutes response.headers.set("Cache-Control", "max-age=1500"); return response; }, }; ``` Explain Code TypeScript ``` export default { async fetch(request): Promise { const url = new URL(request.url); // Only use the path for the cache key, removing query strings // and always store using HTTPS, for example, https://www.example.com/file-uri-here const someCustomKey = `https://${url.hostname}${url.pathname}`; let response = await fetch(request, { cf: { // Always cache this fetch regardless of content type // for a max of 5 seconds before revalidating the resource cacheTtl: 5, cacheEverything: true, //Enterprise only feature, see Cache API for other plans cacheKey: someCustomKey, }, }); // Reconstruct the Response object to make its headers mutable. response = new Response(response.body, response); // Set cache control headers to cache on browser for 25 minutes response.headers.set("Cache-Control", "max-age=1500"); return response; }, } satisfies ExportedHandler; ``` Explain Code TypeScript ``` import { Hono } from 'hono'; type Bindings = {}; const app = new Hono<{ Bindings: Bindings }>(); app.all('*', async (c) => { const url = new URL(c.req.url); // Only use the path for the cache key, removing query strings // and always store using HTTPS, for example, https://www.example.com/file-uri-here const someCustomKey = `https://${url.hostname}${url.pathname}`; // Fetch the request with custom cache settings let response = await fetch(c.req.raw, { cf: { // Always cache this fetch regardless of content type // for a max of 5 seconds before revalidating the resource cacheTtl: 5, cacheEverything: true, // Enterprise only feature, see Cache API for other plans cacheKey: someCustomKey, }, }); // Reconstruct the Response object to make its headers mutable response = new Response(response.body, response); // Set cache control headers to cache on browser for 25 minutes response.headers.set("Cache-Control", "max-age=1500"); return response; }); export default app; ``` Explain Code Python ``` from workers import WorkerEntrypoint from pyodide.ffi import to_js as _to_js from js import Response, URL, Object, fetch def to_js(x): return _to_js(x, dict_converter=Object.fromEntries) class Default(WorkerEntrypoint): async def fetch(self, request): url = URL.new(request.url) # Only use the path for the cache key, removing query strings # and always store using HTTPS, for example, https://www.example.com/file-uri-here some_custom_key = f"https://{url.hostname}{url.pathname}" response = await fetch( request, cf=to_js({ # Always cache this fetch regardless of content type # for a max of 5 seconds before revalidating the resource "cacheTtl": 5, "cacheEverything": True, # Enterprise only feature, see Cache API for other plans "cacheKey": some_custom_key, }), ) # Reconstruct the Response object to make its headers mutable response = Response.new(response.body, response) # Set cache control headers to cache on browser for 25 minutes response.headers["Cache-Control"] = "max-age=1500" return response ``` Explain Code ``` use worker::*; #[event(fetch)] async fn fetch(req: Request, _env: Env, _ctx: Context) -> Result { let url = req.url()?; // Only use the path for the cache key, removing query strings // and always store using HTTPS, for example, https://www.example.com/file-uri-here let custom_key = format!( "https://{host}{path}", host = url.host_str().unwrap(), path = url.path() ); let request = Request::new_with_init( url.as_str(), &RequestInit { headers: req.headers().clone(), method: req.method(), cf: CfProperties { // Always cache this fetch regardless of content type // for a max of 5 seconds before revalidating the resource cache_ttl: Some(5), cache_everything: Some(true), // Enterprise only feature, see Cache API for other plans cache_key: Some(custom_key), ..CfProperties::default() }, ..RequestInit::default() }, )?; let mut response = Fetch::Request(request).send().await?; // Set cache control headers to cache on browser for 25 minutes let _ = response.headers_mut().set("Cache-Control", "max-age=1500"); Ok(response) } ``` Explain Code ## Caching HTML resources JavaScript ``` // Force Cloudflare to cache an asset fetch(event.request, { cf: { cacheEverything: true } }); ``` Setting the cache level to **Cache Everything** will override the default cacheability of the asset. For time-to-live (TTL), Cloudflare will still rely on headers set by the origin. ## Custom cache keys Note This feature is available only to Enterprise customers. A request's cache key is what determines if two requests are the same for caching purposes. If a request has the same cache key as some previous request, then Cloudflare can serve the same cached response for both. For more about cache keys, refer to the [Create custom cache keys](https://developers.cloudflare.com/cache/how-to/cache-keys/#create-custom-cache-keys) documentation. JavaScript ``` // Set cache key for this request to "some-string". fetch(event.request, { cf: { cacheKey: "some-string" } }); ``` Normally, Cloudflare computes the cache key for a request based on the request's URL. Sometimes, though, you may like different URLs to be treated as if they were the same for caching purposes. For example, if your website content is hosted from both Amazon S3 and Google Cloud Storage - you have the same content in both places, and you can use a Worker to randomly balance between the two. However, you do not want to end up caching two copies of your content. You could utilize custom cache keys to cache based on the original request URL rather than the subrequest URL: * [ JavaScript ](#tab-panel-7406) * [ TypeScript ](#tab-panel-7407) * [ Hono ](#tab-panel-7408) JavaScript ``` export default { async fetch(request) { let url = new URL(request.url); if (Math.random() < 0.5) { url.hostname = "example.s3.amazonaws.com"; } else { url.hostname = "example.storage.googleapis.com"; } let newRequest = new Request(url, request); return fetch(newRequest, { cf: { cacheKey: request.url }, }); }, }; ``` Explain Code TypeScript ``` export default { async fetch(request): Promise { let url = new URL(request.url); if (Math.random() < 0.5) { url.hostname = "example.s3.amazonaws.com"; } else { url.hostname = "example.storage.googleapis.com"; } let newRequest = new Request(url, request); return fetch(newRequest, { cf: { cacheKey: request.url }, }); }, } satisfies ExportedHandler; ``` Explain Code TypeScript ``` import { Hono } from 'hono'; type Bindings = {}; const app = new Hono<{ Bindings: Bindings }>(); app.all('*', async (c) => { const originalUrl = c.req.url; const url = new URL(originalUrl); // Randomly select a storage backend if (Math.random() < 0.5) { url.hostname = "example.s3.amazonaws.com"; } else { url.hostname = "example.storage.googleapis.com"; } // Create a new request to the selected backend const newRequest = new Request(url, c.req.raw); // Fetch using the original URL as the cache key return fetch(newRequest, { cf: { cacheKey: originalUrl }, }); }); export default app; ``` Explain Code Workers operating on behalf of different zones cannot affect each other's cache. You can only override cache keys when making requests within your own zone (in the above example `event.request.url` was the key stored), or requests to hosts that are not on Cloudflare. When making a request to another Cloudflare zone (for example, belonging to a different Cloudflare customer), that zone fully controls how its own content is cached within Cloudflare; you cannot override it. ## Override based on origin response code JavaScript ``` // Force response to be cached for 86400 seconds for 200 status // codes, 1 second for 404, and do not cache 500 errors. fetch(request, { cf: { cacheTtlByStatus: { "200-299": 86400, 404: 1, "500-599": 0 } }, }); ``` This option is a version of the `cacheTtl` feature which chooses a TTL based on the response's status code and does not automatically set `cacheEverything: true`. If the response to this request has a status code that matches, Cloudflare will cache for the instructed time, and override cache directives sent by the origin. You can review [details on the cacheTtl feature on the Request page](https://developers.cloudflare.com/workers/runtime-apis/request/#the-cf-property-requestinitcfproperties). ## Customize cache behavior based on request file type Using custom cache keys and overrides based on response code, you can write a Worker that sets the TTL based on the response status code from origin, and request file type. The following example demonstrates how you might use this to cache requests for streaming media assets: * [ Module Worker ](#tab-panel-7409) * [ Service Worker ](#tab-panel-7410) index.js ``` export default { async fetch(request) { // Instantiate new URL to make it mutable const newRequest = new URL(request.url); const customCacheKey = `${newRequest.hostname}${newRequest.pathname}`; const queryCacheKey = `${newRequest.hostname}${newRequest.pathname}${newRequest.search}`; // Different asset types usually have different caching strategies. Most of the time media content such as audio, videos and images that are not user-generated content would not need to be updated often so a long TTL would be best. However, with HLS streaming, manifest files usually are set with short TTLs so that playback will not be affected, as this files contain the data that the player would need. By setting each caching strategy for categories of asset types in an object within an array, you can solve complex needs when it comes to media content for your application const cacheAssets = [ { asset: "video", key: customCacheKey, regex: /(.*\/Video)|(.*\.(m4s|mp4|ts|avi|mpeg|mpg|mkv|bin|webm|vob|flv|m2ts|mts|3gp|m4v|wmv|qt))/, info: 0, ok: 31556952, redirects: 30, clientError: 10, serverError: 0, }, { asset: "image", key: queryCacheKey, regex: /(.*\/Images)|(.*\.(jpg|jpeg|png|bmp|pict|tif|tiff|webp|gif|heif|exif|bat|bpg|ppm|pgn|pbm|pnm))/, info: 0, ok: 3600, redirects: 30, clientError: 10, serverError: 0, }, { asset: "frontEnd", key: queryCacheKey, regex: /^.*\.(css|js)/, info: 0, ok: 3600, redirects: 30, clientError: 10, serverError: 0, }, { asset: "audio", key: customCacheKey, regex: /(.*\/Audio)|(.*\.(flac|aac|mp3|alac|aiff|wav|ogg|aiff|opus|ape|wma|3gp))/, info: 0, ok: 31556952, redirects: 30, clientError: 10, serverError: 0, }, { asset: "directPlay", key: customCacheKey, regex: /.*(\/Download)/, info: 0, ok: 31556952, redirects: 30, clientError: 10, serverError: 0, }, { asset: "manifest", key: customCacheKey, regex: /^.*\.(m3u8|mpd)/, info: 0, ok: 3, redirects: 2, clientError: 1, serverError: 0, }, ]; const { asset, regex, ...cache } = cacheAssets.find(({ regex }) => newRequest.pathname.match(regex)) ?? {}; const newResponse = await fetch(request, { cf: { cacheKey: cache.key, polish: false, cacheEverything: true, cacheTtlByStatus: { "100-199": cache.info, "200-299": cache.ok, "300-399": cache.redirects, "400-499": cache.clientError, "500-599": cache.serverError, }, cacheTags: ["static"], }, }); const response = new Response(newResponse.body, newResponse); // For debugging purposes response.headers.set("debug", JSON.stringify(cache)); return response; }, }; ``` Explain Code Service Workers are deprecated Service Workers are deprecated, but still supported. We recommend using [Module Workers](https://developers.cloudflare.com/workers/reference/migrate-to-module-workers/) instead. New features may not be supported for Service Workers. index.js ``` addEventListener("fetch", (event) => { return event.respondWith(handleRequest(event.request)); }); async function handleRequest(request) { // Instantiate new URL to make it mutable const newRequest = new URL(request.url); // Set `const` to be used in the array later on const customCacheKey = `${newRequest.hostname}${newRequest.pathname}`; const queryCacheKey = `${newRequest.hostname}${newRequest.pathname}${newRequest.search}`; // Set all variables needed to manipulate Cloudflare's cache using the fetch API in the `cf` object. You will be passing these variables in the objects down below. const cacheAssets = [ { asset: "video", key: customCacheKey, regex: /(.*\/Video)|(.*\.(m4s|mp4|ts|avi|mpeg|mpg|mkv|bin|webm|vob|flv|m2ts|mts|3gp|m4v|wmv|qt))/, info: 0, ok: 31556952, redirects: 30, clientError: 10, serverError: 0, }, { asset: "image", key: queryCacheKey, regex: /(.*\/Images)|(.*\.(jpg|jpeg|png|bmp|pict|tif|tiff|webp|gif|heif|exif|bat|bpg|ppm|pgn|pbm|pnm))/, info: 0, ok: 3600, redirects: 30, clientError: 10, serverError: 0, }, { asset: "frontEnd", key: queryCacheKey, regex: /^.*\.(css|js)/, info: 0, ok: 3600, redirects: 30, clientError: 10, serverError: 0, }, { asset: "audio", key: customCacheKey, regex: /(.*\/Audio)|(.*\.(flac|aac|mp3|alac|aiff|wav|ogg|aiff|opus|ape|wma|3gp))/, info: 0, ok: 31556952, redirects: 30, clientError: 10, serverError: 0, }, { asset: "directPlay", key: customCacheKey, regex: /.*(\/Download)/, info: 0, ok: 31556952, redirects: 30, clientError: 10, serverError: 0, }, { asset: "manifest", key: customCacheKey, regex: /^.*\.(m3u8|mpd)/, info: 0, ok: 3, redirects: 2, clientError: 1, serverError: 0, }, ]; // the `.find` method is used to find elements in an array (`cacheAssets`), in this case, `regex`, which can passed to the .`match` method to match on file extensions to cache, since they are many media types in the array. If you want to add more types, update the array. Refer to https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find for more information. const { asset, regex, ...cache } = cacheAssets.find(({ regex }) => newRequest.pathname.match(regex)) ?? {}; const newResponse = await fetch(request, { cf: { cacheKey: cache.key, polish: false, cacheEverything: true, cacheTtlByStatus: { "100-199": cache.info, "200-299": cache.ok, "300-399": cache.redirects, "400-499": cache.clientError, "500-599": cache.serverError, }, cacheTags: ["static"], }, }); const response = new Response(newResponse.body, newResponse); // For debugging purposes response.headers.set("debug", JSON.stringify(cache)); return response; } ``` Explain Code ## Using the HTTP Cache API The `cache` mode can be set in `fetch` options. Currently Workers only support the `no-store` and `no-cache` mode for controlling the cache. When `no-store` is supplied the cache is bypassed on the way to the origin and the request is not cacheable. When `no-cache` is supplied the cache is forced to revalidate the currently cached response with the origin. JavaScript ``` fetch(request, { cache: 'no-store'}); fetch(request, { cache: 'no-cache'}); ``` ```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/cache-using-fetch/","name":"Cache using fetch"}}]} ``` --- --- title: Conditional response description: Return a response based on the incoming request's URL, HTTP method, User Agent, IP address, ASN or device type. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) ### Tags [ Middleware ](https://developers.cloudflare.com/search/?tags=Middleware)[ JavaScript ](https://developers.cloudflare.com/search/?tags=JavaScript)[ TypeScript ](https://developers.cloudflare.com/search/?tags=TypeScript)[ Python ](https://developers.cloudflare.com/search/?tags=Python) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/examples/conditional-response.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Conditional response **Last reviewed:** about 4 years ago Return a response based on the incoming request's URL, HTTP method, User Agent, IP address, ASN or device type. 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/conditional-response) This creates a repository in your GitHub account and deploys the application to Cloudflare Workers. * [ JavaScript ](#tab-panel-7411) * [ TypeScript ](#tab-panel-7412) * [ Python ](#tab-panel-7413) * [ Hono ](#tab-panel-7414) JavaScript ``` export default { async fetch(request) { const BLOCKED_HOSTNAMES = ["nope.mywebsite.com", "bye.website.com"]; // 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); }, }; ``` Explain Code TypeScript ``` export default { async fetch(request): Promise { const BLOCKED_HOSTNAMES = ["nope.mywebsite.com", "bye.website.com"]; // 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); }, } satisfies ExportedHandler; ``` Explain Code Python ``` import re from workers import WorkerEntrypoint, Response, fetch from urllib.parse import urlparse class Default(WorkerEntrypoint): async def fetch(self, request): blocked_hostnames = ["nope.mywebsite.com", "bye.website.com"] url = urlparse(request.url) # Block on hostname if url.hostname in blocked_hostnames: return Response("Blocked Host", status=403) # On paths ending in .doc or .xml if re.search(r'\.(doc|xml)$', url.path): return Response("Blocked Extension", status=403) # On HTTP method if "POST" in request.method: return Response("Response for POST") # On User Agent user_agent = request.headers["User-Agent"] or "" if "bot" in user_agent: return Response("Block User Agent containing bot", status=403) # On Client's IP address client_ip = request.headers["CF-Connecting-IP"] if client_ip == "1.2.3.4": return Response("Block the IP 1.2.3.4", status=403) # On ASN if request.cf and request.cf.asn == 64512: return 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. device = request.headers["CF-Device-Type"] if device == "mobile": return Response.redirect("https://mobile.example.com") return fetch(request) ``` Explain Code TypeScript ``` import { Hono } from "hono"; import { HTTPException } from "hono/http-exception"; const app = new Hono(); // Middleware to handle all conditions before reaching the main handler app.use("*", async (c, next) => { const request = c.req.raw; const BLOCKED_HOSTNAMES = ["nope.mywebsite.com", "bye.website.com"]; const hostname = new URL(c.req.url)?.hostname; // Return a new Response based on a URL's hostname if (BLOCKED_HOSTNAMES.includes(hostname)) { return c.text("Blocked Host", 403); } // Block paths ending in .doc or .xml based on the URL's file extension const forbiddenExtRegExp = new RegExp(/\.(doc|xml)$/); if (forbiddenExtRegExp.test(c.req.pathname)) { return c.text("Blocked Extension", 403); } // On User Agent const userAgent = c.req.header("User-Agent") || ""; if (userAgent.includes("bot")) { return c.text("Block User Agent containing bot", 403); } // On Client's IP address const clientIP = c.req.header("CF-Connecting-IP"); if (clientIP === "1.2.3.4") { return c.text("Block the IP 1.2.3.4", 403); } // On ASN if (request.cf && request.cf.asn === 64512) { return c.text("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 = c.req.header("CF-Device-Type"); if (device === "mobile") { return c.redirect("https://mobile.example.com"); } // Continue to the next handler await next(); }); // Handle POST requests differently app.post("*", (c) => { return c.text("Response for POST"); }); // Default handler for other methods app.get("*", async (c) => { console.error( "Getting Client's IP address, device type, and ASN are not supported in playground. Must test on a live worker", ); // Fetch the original request return fetch(c.req.raw); }); export default app; ``` Explain Code ```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/conditional-response/","name":"Conditional response"}}]} ``` --- --- 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 --- [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) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/examples/cors-header-proxy.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # 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-7415) * [ TypeScript ](#tab-panel-7416) * [ Hono ](#tab-panel-7417) * [ Python ](#tab-panel-7418) * [ Rust ](#tab-panel-7419) 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 = `

API GET without CORS Proxy

Shows TypeError: Failed to fetch since CORS is misconfigured

Waiting

API GET with CORS Proxy

Waiting

API POST with CORS Proxy + Preflight

Waiting `; 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); } }, }; ``` Explain Code TypeScript ``` export default { async fetch(request): Promise { 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 = `

API GET without CORS Proxy

Shows TypeError: Failed to fetch since CORS is misconfigured

Waiting

API GET with CORS Proxy

Waiting

API POST with CORS Proxy + Preflight

Waiting `; 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; ``` Explain Code 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 = `

API GET without CORS Proxy

Shows TypeError: Failed to fetch since CORS is misconfigured

Waiting

API GET with CORS Proxy

Waiting

API POST with CORS Proxy + Preflight

Waiting `; 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; ``` Explain Code 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'''

API GET without CORS Proxy

Shows TypeError: Failed to fetch since CORS is misconfigured

Waiting

API GET with CORS Proxy

Waiting

API POST with CORS Proxy + Preflight

Waiting ''' 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) ``` Explain Code ``` use std::{borrow::Cow, collections::HashMap}; use worker::*; fn raw_html_response(html: &str) -> Result { Response::from_html(html) } async fn handle_request(req: Request, api_url: &str) -> Result { 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 { 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 { 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#"

API GET without CORS Proxy

Shows TypeError: Failed to fetch since CORS is misconfigured

Waiting

API GET with CORS Proxy

Waiting

API POST with CORS Proxy + Preflight

Waiting "# ); 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) } ``` Explain Code ```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"}}]} ``` --- --- title: Country code redirect description: Redirect a response based on the country code in the header of a visitor. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) ### Tags [ Redirects ](https://developers.cloudflare.com/search/?tags=Redirects)[ Geolocation ](https://developers.cloudflare.com/search/?tags=Geolocation)[ JavaScript ](https://developers.cloudflare.com/search/?tags=JavaScript)[ TypeScript ](https://developers.cloudflare.com/search/?tags=TypeScript)[ Python ](https://developers.cloudflare.com/search/?tags=Python) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/examples/country-code-redirect.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Country code redirect **Last reviewed:** over 5 years ago Redirect a response based on the country code in the header of a visitor. 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/country-code-redirect) This creates a repository in your GitHub account and deploys the application to Cloudflare Workers. * [ JavaScript ](#tab-panel-7420) * [ TypeScript ](#tab-panel-7421) * [ Python ](#tab-panel-7422) * [ Hono ](#tab-panel-7423) JavaScript ``` export default { async fetch(request) { /** * A map of the URLs to redirect to * @param {Object} countryMap */ const countryMap = { US: "https://example.com/us", EU: "https://example.com/eu", }; // Use the cf object to obtain the country of the request // more on the cf object: https://developers.cloudflare.com/workers/runtime-apis/request#incomingrequestcfproperties const country = request.cf.country; if (country != null && country in countryMap) { const url = countryMap[country]; // Remove this logging statement from your final output. console.log( `Based on ${country}-based request, your user would go to ${url}.`, ); return Response.redirect(url); } else { return fetch("https://example.com", request); } }, }; ``` Explain Code TypeScript ``` export default { async fetch(request): Promise { /** * A map of the URLs to redirect to * @param {Object} countryMap */ const countryMap = { US: "https://example.com/us", EU: "https://example.com/eu", }; // Use the cf object to obtain the country of the request // more on the cf object: https://developers.cloudflare.com/workers/runtime-apis/request#incomingrequestcfproperties const country = request.cf.country; if (country != null && country in countryMap) { const url = countryMap[country]; return Response.redirect(url); } else { return fetch(request); } }, } satisfies ExportedHandler; ``` Explain Code Python ``` from workers import WorkerEntrypoint, Response, fetch class Default(WorkerEntrypoint): async def fetch(self, request): countries = { "US": "https://example.com/us", "EU": "https://example.com/eu", } # Use the cf object to obtain the country of the request # more on the cf object: https://developers.cloudflare.com/workers/runtime-apis/request#incomingrequestcfproperties country = request.cf.country if country and country in countries: url = countries[country] return Response.redirect(url) return fetch("https://example.com", request) ``` Explain Code TypeScript ``` import { Hono } from 'hono'; // Define the RequestWithCf interface to add Cloudflare-specific properties interface RequestWithCf extends Request { cf: { country: string; // Other CF properties can be added as needed }; } const app = new Hono(); app.get('*', async (c) => { /** * A map of the URLs to redirect to */ const countryMap: Record = { US: "https://example.com/us", EU: "https://example.com/eu", }; // Cast the raw request to include Cloudflare-specific properties const request = c.req.raw as RequestWithCf; // Use the cf object to obtain the country of the request // more on the cf object: https://developers.cloudflare.com/workers/runtime-apis/request#incomingrequestcfproperties const country = request.cf.country; if (country != null && country in countryMap) { const url = countryMap[country]; // Redirect using Hono's redirect helper return c.redirect(url); } else { // Default fallback return fetch("https://example.com", request); } }); export default app; ``` Explain Code ```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/country-code-redirect/","name":"Country code redirect"}}]} ``` --- --- title: Setting Cron Triggers description: Set a Cron Trigger for your Worker. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) ### Tags [ Middleware ](https://developers.cloudflare.com/search/?tags=Middleware)[ JavaScript ](https://developers.cloudflare.com/search/?tags=JavaScript)[ TypeScript ](https://developers.cloudflare.com/search/?tags=TypeScript) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/examples/cron-trigger.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Setting Cron Triggers **Last reviewed:** over 4 years ago Set a Cron Trigger for your Worker. * [ JavaScript ](#tab-panel-7424) * [ TypeScript ](#tab-panel-7425) * [ Python ](#tab-panel-7426) * [ Hono ](#tab-panel-7427) JavaScript ``` export default { async scheduled(controller, env, ctx) { console.log("cron processed"); }, }; ``` TypeScript ``` interface Env {} export default { async scheduled( controller: ScheduledController, env: Env, ctx: ExecutionContext, ) { console.log("cron processed"); }, }; ``` Explain Code Python ``` from workers import WorkerEntrypoint, Response class Default(WorkerEntrypoint): async def scheduled(self, controller, env, ctx): print("cron processed") ``` TypeScript ``` import { Hono } from "hono"; interface Env {} // Create Hono app const app = new Hono<{ Bindings: Env }>(); // Regular routes for normal HTTP requests app.get("/", (c) => c.text("Hello World!")); // Export both the app and a scheduled function export default { // The Hono app handles regular HTTP requests fetch: app.fetch, // The scheduled function handles Cron triggers async scheduled( controller: ScheduledController, env: Env, ctx: ExecutionContext, ) { console.log("cron processed"); // You could also perform actions like: // - Fetching data from external APIs // - Updating KV or Durable Object storage // - Running maintenance tasks // - Sending notifications }, }; ``` Explain Code ## Set Cron Triggers in Wrangler Refer to [Cron Triggers](https://developers.cloudflare.com/workers/configuration/cron-triggers/) for more information on how to add a Cron Trigger. If you are deploying with Wrangler, set the cron syntax (once per hour as shown below) by adding this to your Wrangler file: * [ wrangler.jsonc ](#tab-panel-7428) * [ wrangler.toml ](#tab-panel-7429) JSONC ``` { "$schema": "./node_modules/wrangler/config-schema.json", "name": "worker", // ... "triggers": { "crons": [ "0 * * * *" ] } } ``` Explain Code TOML ``` "$schema" = "./node_modules/wrangler/config-schema.json" name = "worker" [triggers] crons = [ "0 * * * *" ] ``` You also can set a different Cron Trigger for each [environment](https://developers.cloudflare.com/workers/wrangler/environments/) in your [Wrangler configuration file](https://developers.cloudflare.com/workers/wrangler/configuration/). You need to put the `[triggers]` table under your chosen environment. For example: * [ wrangler.jsonc ](#tab-panel-7430) * [ wrangler.toml ](#tab-panel-7431) JSONC ``` { "env": { "dev": { "triggers": { "crons": [ "0 * * * *" ] } } } } ``` Explain Code TOML ``` [env.dev.triggers] crons = [ "0 * * * *" ] ``` ## Test Cron Triggers using Wrangler The recommended way of testing Cron Triggers is using Wrangler. Cron Triggers can be tested using Wrangler by passing in the `--test-scheduled` flag to [wrangler dev](https://developers.cloudflare.com/workers/wrangler/commands/general/#dev). This will expose a `/__scheduled` (or `/cdn-cgi/handler/scheduled` for Python Workers) route which can be used to test using a HTTP request. To simulate different cron patterns, a `cron` query parameter can be passed in. Terminal window ``` npx wrangler dev --test-scheduled curl "http://localhost:8787/__scheduled?cron=0+*+*+*+*" curl "http://localhost:8787/cdn-cgi/handler/scheduled?cron=*+*+*+*+*" # Python Workers ``` ```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/cron-trigger/","name":"Setting Cron Triggers"}}]} ``` --- --- title: Data loss prevention description: Protect sensitive data to prevent data loss, and send alerts to a webhooks server in the event of a data breach. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) ### Tags [ Security ](https://developers.cloudflare.com/search/?tags=Security)[ JavaScript ](https://developers.cloudflare.com/search/?tags=JavaScript)[ TypeScript ](https://developers.cloudflare.com/search/?tags=TypeScript)[ Python ](https://developers.cloudflare.com/search/?tags=Python) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/examples/data-loss-prevention.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Data loss prevention **Last reviewed:** over 5 years ago Protect sensitive data to prevent data loss, and send alerts to a webhooks server in the event of a data breach. 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/data-loss-prevention) This creates a repository in your GitHub account and deploys the application to Cloudflare Workers. * [ JavaScript ](#tab-panel-7432) * [ TypeScript ](#tab-panel-7433) * [ Python ](#tab-panel-7434) * [ Hono ](#tab-panel-7435) JavaScript ``` export default { async fetch(request) { const DEBUG = true; const SOME_HOOK_SERVER = "https://webhook.flow-wolf.io/hook"; /** * Alert a data breach by posting to a webhook server */ async function postDataBreach(request) { return await fetch(SOME_HOOK_SERVER, { method: "POST", headers: { "content-type": "application/json;charset=UTF-8", }, body: JSON.stringify({ ip: request.headers.get("cf-connecting-ip"), time: Date.now(), request: request, }), }); } /** * 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/*". */ 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); }, }; ``` Explain Code TypeScript ``` export default { async fetch(request): Promise { const DEBUG = true; const SOME_HOOK_SERVER = "https://webhook.flow-wolf.io/hook"; /** * Alert a data breach by posting to a webhook server */ async function postDataBreach(request) { return await fetch(SOME_HOOK_SERVER, { method: "POST", headers: { "content-type": "application/json;charset=UTF-8", }, body: JSON.stringify({ ip: request.headers.get("cf-connecting-ip"), time: Date.now(), request: request, }), }); } /** * 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/*". */ 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); }, } satisfies ExportedHandler; ``` Explain Code Python ``` import re from workers import WorkerEntrypoint from datetime import datetime from js import Response, fetch, JSON, Headers # Alert a data breach by posting to a webhook server async def post_data_breach(request): some_hook_server = "https://webhook.flow-wolf.io/hook" headers = Headers.new({"content-type": "application/json"}.items()) body = JSON.stringify({ "ip": request.headers["cf-connecting-ip"], "time": datetime.now(), "request": request, }) return await fetch(some_hook_server, method="POST", headers=headers, body=body) class Default(WorkerEntrypoint): async def fetch(self, request): debug = True # 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/*". response = await fetch(request) # Return origin response, if response wasn’t text content_type = response.headers["content-type"] or "" if "text" not in content_type: return response text = await response.text() # When debugging replace the response from the origin with an email text = text.replace("You may use this", "me@example.com may use this") if debug else text sensitive_regex = [ ("credit_card", r'\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", r'\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b'), ("phone", r'\b07\d{9}\b'), ] for (kind, regex) in sensitive_regex: match = re.search(regex, text, flags=re.IGNORECASE) if match: # Alert a data breach await post_data_breach(request) # Respond with a block if credit card, otherwise replace sensitive text with `*`s card_resp = Response.new(kind + " found\nForbidden\n", status=403,statusText="Forbidden") sensitive_resp = Response.new(re.sub(regex, "*"*10, text, flags=re.IGNORECASE), response) return card_resp if kind == "credit_card" else sensitive_resp return Response.new(text, response) ``` Explain Code TypeScript ``` import { Hono } from 'hono'; const app = new Hono(); // Configuration const DEBUG = true; const SOME_HOOK_SERVER = "https://webhook.flow-wolf.io/hook"; // Define sensitive data patterns 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`, }; /** * Alert a data breach by posting to a webhook server */ async function postDataBreach(request: Request) { return await fetch(SOME_HOOK_SERVER, { method: "POST", headers: { "content-type": "application/json;charset=UTF-8", }, body: JSON.stringify({ ip: request.headers.get("cf-connecting-ip"), time: Date.now(), request: request, }), }); } // Main middleware to handle data loss prevention app.use('*', async (c) => { // Fetch the origin response const response = await fetch(c.req.raw); // Return origin response if response wasn't text const contentType = response.headers.get("content-type") || ""; if (!contentType.toLowerCase().includes("text/")) { return response; } // Get the response text 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; // Check for sensitive data for (const kind in sensitiveRegexsMap) { const sensitiveRegex = new RegExp(sensitiveRegexsMap[kind], "ig"); const match = sensitiveRegex.test(text); if (match) { // Alert a data breach await postDataBreach(c.req.raw); // Respond with a block if credit card, otherwise replace sensitive text with `*`s if (kind === "creditCard") { return c.text(`${kind} found\nForbidden\n`, 403); } else { return new Response(text.replace(sensitiveRegex, "**********"), { status: response.status, statusText: response.statusText, headers: response.headers, }); } } } // Return the modified response return new Response(text, { status: response.status, statusText: response.statusText, headers: response.headers, }); }); export default app; ``` Explain Code ```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/data-loss-prevention/","name":"Data loss prevention"}}]} ``` --- --- title: Debugging logs description: Send debugging information in an errored response to a logging service. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) ### Tags [ Debugging ](https://developers.cloudflare.com/search/?tags=Debugging)[ JavaScript ](https://developers.cloudflare.com/search/?tags=JavaScript)[ TypeScript ](https://developers.cloudflare.com/search/?tags=TypeScript)[ Python ](https://developers.cloudflare.com/search/?tags=Python) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/examples/debugging-logs.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Debugging logs **Last reviewed:** over 5 years ago Send debugging information in an errored response to a logging service. 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/debugging-logs) This creates a repository in your GitHub account and deploys the application to Cloudflare Workers. * [ JavaScript ](#tab-panel-7436) * [ TypeScript ](#tab-panel-7437) * [ Python ](#tab-panel-7438) * [ Hono ](#tab-panel-7439) JavaScript ``` export default { async fetch(request, env, ctx) { // Service configured to receive logs const LOG_URL = "https://log-service.example.com/"; async function postLog(data) { return await fetch(LOG_URL, { method: "POST", body: data, }); } let response; try { response = await fetch(request); if (!response.ok && !response.redirected) { const body = await response.text(); throw new Error( "Bad response at origin. Status: " + response.status + " Body: " + // Ensure the string is small enough to be a header body.trim().substring(0, 10), ); } } catch (err) { // Without ctx.waitUntil(), your fetch() to Cloudflare's // logging service may or may not complete ctx.waitUntil(postLog(err.toString())); const stack = JSON.stringify(err.stack) || err; // Copy the response and initialize body to the stack trace response = new Response(stack, response); // Add the error stack into a header to find out what happened response.headers.set("X-Debug-stack", stack); response.headers.set("X-Debug-err", err); } return response; }, }; ``` Explain Code TypeScript ``` interface Env {} export default { async fetch(request, env, ctx): Promise { // Service configured to receive logs const LOG_URL = "https://log-service.example.com/"; async function postLog(data) { return await fetch(LOG_URL, { method: "POST", body: data, }); } let response; try { response = await fetch(request); if (!response.ok && !response.redirected) { const body = await response.text(); throw new Error( "Bad response at origin. Status: " + response.status + " Body: " + // Ensure the string is small enough to be a header body.trim().substring(0, 10), ); } } catch (err) { // Without ctx.waitUntil(), your fetch() to Cloudflare's // logging service may or may not complete ctx.waitUntil(postLog(err.toString())); const stack = JSON.stringify(err.stack) || err; // Copy the response and initialize body to the stack trace response = new Response(stack, response); // Add the error stack into a header to find out what happened response.headers.set("X-Debug-stack", stack); response.headers.set("X-Debug-err", err); } return response; }, } satisfies ExportedHandler; ``` Explain Code Python ``` from workers import WorkerEntrypoint from pyodide.ffi import create_proxy from js import Response, fetch async def post_log(data): log_url = "https://log-service.example.com/" await fetch(log_url, method="POST", body=data) class Default(WorkerEntrypoint): async def fetch(self, request): # Service configured to receive logs response = await fetch(request) try: if not response.ok and not response.redirected: body = await response.text() # Simulating an error. Ensure the string is small enough to be a header raise Exception(f'Bad response at origin. Status:{response.status} Body:{body.strip()[:10]}') except Exception as e: # Without ctx.waitUntil(), your fetch() to Cloudflare's # logging service may or may not complete self.ctx.waitUntil(create_proxy(post_log(str(e)))) # Copy the response and add to header response = Response.new(stack, response) response.headers["X-Debug-err"] = str(e) return response ``` Explain Code TypeScript ``` import { Hono } from 'hono'; // Define the environment with appropriate types interface Env {} const app = new Hono<{ Bindings: Env }>(); // Service configured to receive logs const LOG_URL = "https://log-service.example.com/"; // Function to post logs to an external service async function postLog(data: string) { return await fetch(LOG_URL, { method: "POST", body: data, }); } // Middleware to handle error logging app.use('*', async (c, next) => { try { // Process the request with the next handler await next(); // After processing, check if the response indicates an error if (c.res && (!c.res.ok && !c.res.redirected)) { const body = await c.res.clone().text(); throw new Error( "Bad response at origin. Status: " + c.res.status + " Body: " + // Ensure the string is small enough to be a header body.trim().substring(0, 10) ); } } catch (err) { // Without waitUntil, the fetch to the logging service may not complete c.executionCtx.waitUntil( postLog(err.toString()) ); // Get the error stack or error itself const stack = JSON.stringify(err.stack) || err.toString(); // Create a new response with the error information const response = c.res ? new Response(stack, { status: c.res.status, headers: c.res.headers }) : new Response(stack, { status: 500 }); // Add debug headers response.headers.set("X-Debug-stack", stack); response.headers.set("X-Debug-err", err.toString()); // Set the modified response c.res = response; } }); // Default route handler that passes requests through app.all('*', async (c) => { return fetch(c.req.raw); }); export default app; ``` Explain Code ```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/debugging-logs/","name":"Debugging logs"}}]} ``` --- --- title: Cookie parsing description: Given the cookie name, get the value of a cookie. You can also use cookies for A/B testing. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) ### Tags [ 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) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/examples/extract-cookie-value.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Cookie parsing **Last reviewed:** about 4 years ago Given the cookie name, get the value of a cookie. You can also use cookies for A/B testing. 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/extract-cookie-value) This creates a repository in your GitHub account and deploys the application to Cloudflare Workers. * [ JavaScript ](#tab-panel-7440) * [ TypeScript ](#tab-panel-7441) * [ Python ](#tab-panel-7442) * [ Hono ](#tab-panel-7443) JavaScript ``` import { parse } from "cookie"; export default { async fetch(request) { // The name of the cookie const COOKIE_NAME = "__uid"; const cookie = parse(request.headers.get("Cookie") || ""); if (cookie[COOKIE_NAME] != null) { // Respond with the cookie value return new Response(cookie[COOKIE_NAME]); } return new Response("No cookie with name: " + COOKIE_NAME); }, }; ``` Explain Code TypeScript ``` import { parse } from "cookie"; export default { async fetch(request): Promise { // The name of the cookie const COOKIE_NAME = "__uid"; const cookie = parse(request.headers.get("Cookie") || ""); if (cookie[COOKIE_NAME] != null) { // Respond with the cookie value return new Response(cookie[COOKIE_NAME]); } return new Response("No cookie with name: " + COOKIE_NAME); }, } satisfies ExportedHandler; ``` Explain Code Python ``` from http.cookies import SimpleCookie from workers import WorkerEntrypoint, Response class Default(WorkerEntrypoint): async def fetch(self, request): # Name of the cookie cookie_name = "__uid" cookies = SimpleCookie(request.headers["Cookie"] or "") if cookie_name in cookies: # Respond with cookie value return Response(cookies[cookie_name].value) return Response("No cookie with name: " + cookie_name) ``` Explain Code TypeScript ``` import { Hono } from 'hono'; import { getCookie } from 'hono/cookie'; const app = new Hono(); app.get('*', (c) => { // The name of the cookie const COOKIE_NAME = "__uid"; // Get the specific cookie value using Hono's cookie helper const cookieValue = getCookie(c, COOKIE_NAME); if (cookieValue) { // Respond with the cookie value return c.text(cookieValue); } return c.text("No cookie with name: " + COOKIE_NAME); }); export default app; ``` Explain Code External dependencies This example requires the npm package [cookie ↗](https://www.npmjs.com/package/cookie) to be installed in your JavaScript project. The Hono example uses the built-in cookie utilities provided by Hono, so no external dependencies are needed for that implementation. ```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/extract-cookie-value/","name":"Cookie parsing"}}]} ``` --- --- title: Fetch HTML description: Send a request to a remote server, read HTML from the response, and serve that HTML. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) ### Tags [ JavaScript ](https://developers.cloudflare.com/search/?tags=JavaScript)[ TypeScript ](https://developers.cloudflare.com/search/?tags=TypeScript)[ Python ](https://developers.cloudflare.com/search/?tags=Python) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/examples/fetch-html.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Fetch HTML **Last reviewed:** over 2 years ago Send a request to a remote server, read HTML from the response, and serve that HTML. 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/fetch-html) This creates a repository in your GitHub account and deploys the application to Cloudflare Workers. * [ JavaScript ](#tab-panel-7444) * [ TypeScript ](#tab-panel-7445) * [ Python ](#tab-panel-7446) * [ Hono ](#tab-panel-7447) JavaScript ``` export default { async fetch(request) { /** * Replace `remote` with the host you wish to send requests to */ const remote = "https://example.com"; return await fetch(remote, request); }, }; ``` [Run Worker in Playground](https://workers.cloudflare.com/playground#LYVwNgLglgDghgJwgegGYHsHALQBM4RwDcABAEbogB2+CAngLzbPYZb6HbW5QDGU2AAwA2AIwBOAEwAWAKzDx4sQC4WLNsA5wuNPgJESZ8xWICwAKADC6KhACmt7ABEoAZxjpXUaDeUkNeATEJFRwwHYMAERQNHYAHgB0AFaukaSoUGAOYRHRsYkpkRbWtg4Q2AAqdDB2fnAwMGB8BFA2yElwAG5wrrwIsBAA1MDouOB2FhbxHkgkuHaocOAQJADe5iQkPXRUvP52ELwAFgAUCHYAjiB2rhAAlGsbmyTIAFSvT8+vJABKdo1wXh2EgAA3OI3sIJIAHdvEcSBAjsCjp4VnRKDC3PCIOgSK4HLgSOcrjcIK4EehPptXsgqbwbLciXYIcCGCRIkcIBAYK5lMhkPEwo07Al6cA0uYqecICAEFQttC4N59odTuD0PYADRMkm3O5EJ4AX015kNRAsamYGi0Oh4-CEYikcgUSlExRs9kcLncnm8rSofgCWlIoXCUXChC0aX8mWyYciZDA6DIRSsHrKlWqtS2DSavBabRSNkm5lWkWASqoAH0RmMspFlHl5gVUoaLZbrUFbXoHYZnSZRMwLEA) TypeScript ``` export default { async fetch(request: Request): Promise { /** * Replace `remote` with the host you wish to send requests to */ const remote = "https://example.com"; return await fetch(remote, request); }, }; ``` Explain Code Python ``` from workers import WorkerEntrypoint from js import fetch class Default(WorkerEntrypoint): async def fetch(self, request): # Replace `remote` with the host you wish to send requests to remote = "https://example.com" return await fetch(remote, request) ``` TypeScript ``` import { Hono } from "hono"; const app = new Hono(); app.all("*", async (c) => { /** * Replace `remote` with the host you wish to send requests to */ const remote = "https://example.com"; // Forward the request to the remote server return await fetch(remote, c.req.raw); }); export default app; ``` Explain Code ```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/fetch-html/","name":"Fetch HTML"}}]} ``` --- --- title: Fetch JSON description: Send a GET request and read in JSON from the response. Use to fetch external data. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) ### Tags [ JSON ](https://developers.cloudflare.com/search/?tags=JSON)[ JavaScript ](https://developers.cloudflare.com/search/?tags=JavaScript)[ TypeScript ](https://developers.cloudflare.com/search/?tags=TypeScript)[ Python ](https://developers.cloudflare.com/search/?tags=Python) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/examples/fetch-json.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Fetch JSON **Last reviewed:** about 4 years ago Send a GET request and read in JSON from the response. Use to fetch external data. 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/fetch-json) This creates a repository in your GitHub account and deploys the application to Cloudflare Workers. * [ JavaScript ](#tab-panel-7448) * [ TypeScript ](#tab-panel-7449) * [ Python ](#tab-panel-7450) * [ Hono ](#tab-panel-7451) JavaScript ``` export default { async fetch(request, env, ctx) { const url = "https://jsonplaceholder.typicode.com/todos/1"; // gatherResponse returns both content-type & response body as a string async function gatherResponse(response) { const { headers } = response; const contentType = headers.get("content-type") || ""; if (contentType.includes("application/json")) { return { contentType, result: JSON.stringify(await response.json()) }; } return { contentType, result: await response.text() }; } const response = await fetch(url); const { contentType, result } = await gatherResponse(response); const options = { headers: { "content-type": contentType } }; return new Response(result, options); }, }; ``` Explain Code TypeScript ``` interface Env {} export default { async fetch(request, env, ctx): Promise { const url = "https://jsonplaceholder.typicode.com/todos/1"; // gatherResponse returns both content-type & response body as a string async function gatherResponse(response) { const { headers } = response; const contentType = headers.get("content-type") || ""; if (contentType.includes("application/json")) { return { contentType, result: JSON.stringify(await response.json()) }; } return { contentType, result: await response.text() }; } const response = await fetch(url); const { contentType, result } = await gatherResponse(response); const options = { headers: { "content-type": contentType } }; return new Response(result, options); }, } satisfies ExportedHandler; ``` Explain Code Python ``` from workers import WorkerEntrypoint, Response, fetch import json class Default(WorkerEntrypoint): async def fetch(self, request): url = "https://jsonplaceholder.typicode.com/todos/1" # gather_response returns both content-type & response body as a string async def gather_response(response): headers = response.headers content_type = headers["content-type"] or "" if "application/json" in content_type: return (content_type, json.dumps(await response.json())) return (content_type, await response.text()) response = await fetch(url) content_type, result = await gather_response(response) headers = {"content-type": content_type} return Response(result, headers=headers) ``` Explain Code TypeScript ``` import { Hono } from 'hono'; type Env = {}; const app = new Hono<{ Bindings: Env }>(); app.get('*', async (c) => { const url = "https://jsonplaceholder.typicode.com/todos/1"; // gatherResponse returns both content-type & response body as a string async function gatherResponse(response: Response) { const { headers } = response; const contentType = headers.get("content-type") || ""; if (contentType.includes("application/json")) { return { contentType, result: JSON.stringify(await response.json()) }; } return { contentType, result: await response.text() }; } const response = await fetch(url); const { contentType, result } = await gatherResponse(response); return new Response(result, { headers: { "content-type": contentType } }); }); export default app; ``` Explain Code ```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/fetch-json/","name":"Fetch JSON"}}]} ``` --- --- title: Geolocation: Weather application description: Fetch weather data from an API using the user's geolocation data. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) ### Tags [ Geolocation ](https://developers.cloudflare.com/search/?tags=Geolocation)[ JavaScript ](https://developers.cloudflare.com/search/?tags=JavaScript)[ TypeScript ](https://developers.cloudflare.com/search/?tags=TypeScript)[ Python ](https://developers.cloudflare.com/search/?tags=Python) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/examples/geolocation-app-weather.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Geolocation: Weather application **Last reviewed:** about 5 years ago Fetch weather data from an API using the user's geolocation data. 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/geolocation-app-weather) This creates a repository in your GitHub account and deploys the application to Cloudflare Workers. * [ JavaScript ](#tab-panel-7452) * [ TypeScript ](#tab-panel-7453) * [ Hono ](#tab-panel-7454) * [ Python ](#tab-panel-7455) JavaScript ``` export default { async fetch(request) { let endpoint = "https://api.waqi.info/feed/geo:"; const token = ""; //Use a token from https://aqicn.org/api/ let html_style = `body{padding:6em; font-family: sans-serif;} h1{color:#f6821f}`; let html_content = "

Weather 🌦

"; const latitude = request.cf.latitude; const longitude = request.cf.longitude; endpoint += `${latitude};${longitude}/?token=${token}`; const init = { headers: { "content-type": "application/json;charset=UTF-8", }, }; const response = await fetch(endpoint, init); const content = await response.json(); html_content += `

This is a demo using Workers geolocation data.

`; html_content += `You are located at: ${latitude},${longitude}.

`; html_content += `

Based off sensor data from ${content.data.city.url}">${content.data.city.name}:

`; html_content += `

The AQI level is: ${content.data.aqi}.

`; html_content += `

The N02 level is: ${content.data.iaqi.no2?.v}.

`; html_content += `

The O3 level is: ${content.data.iaqi.o3?.v}.

`; html_content += `

The temperature is: ${content.data.iaqi.t?.v}°C.

`; let html = ` Geolocation: Weather
${html_content}
`; return new Response(html, { headers: { "content-type": "text/html;charset=UTF-8", }, }); }, }; ``` Explain Code TypeScript ``` export default { async fetch(request): Promise { let endpoint = "https://api.waqi.info/feed/geo:"; const token = ""; //Use a token from https://aqicn.org/api/ let html_style = `body{padding:6em; font-family: sans-serif;} h1{color:#f6821f}`; let html_content = "

Weather 🌦

"; const latitude = request.cf.latitude; const longitude = request.cf.longitude; endpoint += `${latitude};${longitude}/?token=${token}`; const init = { headers: { "content-type": "application/json;charset=UTF-8", }, }; const response = await fetch(endpoint, init); const content = await response.json(); html_content += `

This is a demo using Workers geolocation data.

`; html_content += `You are located at: ${latitude},${longitude}.

`; html_content += `

Based off sensor data from ${content.data.city.url}">${content.data.city.name}:

`; html_content += `

The AQI level is: ${content.data.aqi}.

`; html_content += `

The N02 level is: ${content.data.iaqi.no2?.v}.

`; html_content += `

The O3 level is: ${content.data.iaqi.o3?.v}.

`; html_content += `

The temperature is: ${content.data.iaqi.t?.v}°C.

`; let html = ` Geolocation: Weather
${html_content}
`; return new Response(html, { headers: { "content-type": "text/html;charset=UTF-8", }, }); }, } satisfies ExportedHandler; ``` Explain Code TypeScript ``` import { Hono } from 'hono'; import { html } from 'hono/html'; type Bindings = {}; interface WeatherApiResponse { data: { aqi: number; city: { name: string; url: string; }; iaqi: { no2?: { v: number }; o3?: { v: number }; t?: { v: number }; }; }; } const app = new Hono<{ Bindings: Bindings }>(); app.get('*', async (c) => { // Get API endpoint let endpoint = "https://api.waqi.info/feed/geo:"; const token = ""; // Use a token from https://aqicn.org/api/ // Define styles const html_style = `body{padding:6em; font-family: sans-serif;} h1{color:#f6821f}`; // Get geolocation from Cloudflare request const req = c.req.raw; const latitude = req.cf?.latitude; const longitude = req.cf?.longitude; // Create complete API endpoint with coordinates endpoint += `${latitude};${longitude}/?token=${token}`; // Fetch weather data const init = { headers: { "content-type": "application/json;charset=UTF-8", }, }; const response = await fetch(endpoint, init); const content = await response.json() as WeatherApiResponse; // Build HTML content const weatherContent = html`

Weather 🌦

This is a demo using Workers geolocation data.

You are located at: ${latitude},${longitude}.

Based off sensor data from ${content.data.city.url}">${content.data.city.name}:

The AQI level is: ${content.data.aqi}.

The N02 level is: ${content.data.iaqi.no2?.v}.

The O3 level is: ${content.data.iaqi.o3?.v}.

The temperature is: ${content.data.iaqi.t?.v}°C.

`; // Complete HTML document const htmlDocument = html` Geolocation: Weather
${weatherContent}
`; // Return HTML response return c.html(htmlDocument); }); export default app; ``` Explain Code Python ``` from workers import WorkerEntrypoint, Response, fetch class Default(WorkerEntrypoint): async def fetch(self, request): endpoint = "https://api.waqi.info/feed/geo:" token = "" # Use a token from https://aqicn.org/api/ html_style = "body{padding:6em; font-family: sans-serif;} h1{color:#f6821f}" html_content = "

Weather 🌦

" latitude = request.cf.latitude longitude = request.cf.longitude endpoint += f"{latitude};{longitude}/?token={token}" response = await fetch(endpoint) content = await response.json() html_content += "

This is a demo using Workers geolocation data.

" html_content += f"You are located at: {latitude},{longitude}.

" html_content += f"

Based off sensor data from {content['data']['city']['name']}:

" html_content += f"

The AQI level is: {content['data']['aqi']}.

" html_content += f"

The N02 level is: {content['data']['iaqi']['no2']['v']}.

" html_content += f"

The O3 level is: {content['data']['iaqi']['o3']['v']}.

" html_content += f"

The temperature is: {content['data']['iaqi']['t']['v']}°C.

" html = f""" Geolocation: Weather
{html_content}
""" headers = {"content-type": "text/html;charset=UTF-8"} return Response(html, headers=headers) ``` Explain Code ```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/geolocation-app-weather/","name":"Geolocation: Weather application"}}]} ``` --- --- title: Geolocation: Custom Styling description: Personalize website styling based on localized user time. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) ### Tags [ Geolocation ](https://developers.cloudflare.com/search/?tags=Geolocation)[ JavaScript ](https://developers.cloudflare.com/search/?tags=JavaScript)[ TypeScript ](https://developers.cloudflare.com/search/?tags=TypeScript) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/examples/geolocation-custom-styling.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Geolocation: Custom Styling **Last reviewed:** about 4 years ago Personalize website styling based on localized user time. 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/geolocation-custom-styling) This creates a repository in your GitHub account and deploys the application to Cloudflare Workers. * [ JavaScript ](#tab-panel-7456) * [ TypeScript ](#tab-panel-7457) * [ Hono ](#tab-panel-7458) JavaScript ``` export default { async fetch(request) { let grads = [ [ { color: "00000c", position: 0 }, { color: "00000c", position: 0 }, ], [ { color: "020111", position: 85 }, { color: "191621", position: 100 }, ], [ { color: "020111", position: 60 }, { color: "20202c", position: 100 }, ], [ { color: "020111", position: 10 }, { color: "3a3a52", position: 100 }, ], [ { color: "20202c", position: 0 }, { color: "515175", position: 100 }, ], [ { color: "40405c", position: 0 }, { color: "6f71aa", position: 80 }, { color: "8a76ab", position: 100 }, ], [ { color: "4a4969", position: 0 }, { color: "7072ab", position: 50 }, { color: "cd82a0", position: 100 }, ], [ { color: "757abf", position: 0 }, { color: "8583be", position: 60 }, { color: "eab0d1", position: 100 }, ], [ { color: "82addb", position: 0 }, { color: "ebb2b1", position: 100 }, ], [ { color: "94c5f8", position: 1 }, { color: "a6e6ff", position: 70 }, { color: "b1b5ea", position: 100 }, ], [ { color: "b7eaff", position: 0 }, { color: "94dfff", position: 100 }, ], [ { color: "9be2fe", position: 0 }, { color: "67d1fb", position: 100 }, ], [ { color: "90dffe", position: 0 }, { color: "38a3d1", position: 100 }, ], [ { color: "57c1eb", position: 0 }, { color: "246fa8", position: 100 }, ], [ { color: "2d91c2", position: 0 }, { color: "1e528e", position: 100 }, ], [ { color: "2473ab", position: 0 }, { color: "1e528e", position: 70 }, { color: "5b7983", position: 100 }, ], [ { color: "1e528e", position: 0 }, { color: "265889", position: 50 }, { color: "9da671", position: 100 }, ], [ { color: "1e528e", position: 0 }, { color: "728a7c", position: 50 }, { color: "e9ce5d", position: 100 }, ], [ { color: "154277", position: 0 }, { color: "576e71", position: 30 }, { color: "e1c45e", position: 70 }, { color: "b26339", position: 100 }, ], [ { color: "163C52", position: 0 }, { color: "4F4F47", position: 30 }, { color: "C5752D", position: 60 }, { color: "B7490F", position: 80 }, { color: "2F1107", position: 100 }, ], [ { color: "071B26", position: 0 }, { color: "071B26", position: 30 }, { color: "8A3B12", position: 80 }, { color: "240E03", position: 100 }, ], [ { color: "010A10", position: 30 }, { color: "59230B", position: 80 }, { color: "2F1107", position: 100 }, ], [ { color: "090401", position: 50 }, { color: "4B1D06", position: 100 }, ], [ { color: "00000c", position: 80 }, { color: "150800", position: 100 }, ], ]; async function toCSSGradient(hour) { let css = "linear-gradient(to bottom,"; const data = grads[hour]; const len = data.length; for (let i = 0; i < len; i++) { const item = data[i]; css += ` #${item.color} ${item.position}%`; if (i < len - 1) css += ","; } return css + ")"; } let html_content = ""; let html_style = ` html{width:100vw; height:100vh;} body{padding:0; margin:0 !important;height:100%;} #container { display: flex; flex-direction:column; align-items: center; justify-content: center; height: 100%; color:white; font-family:sans-serif; }`; const timezone = request.cf.timezone; console.log(timezone); let localized_date = new Date( new Date().toLocaleString("en-US", { timeZone: timezone }), ); let hour = localized_date.getHours(); let minutes = localized_date.getMinutes(); html_content += "

" + hour + ":" + minutes + "

"; html_content += "

" + timezone + "

"; html_style += "body{background:" + (await toCSSGradient(hour)) + ";}"; let html = ` Geolocation: Customized Design
${html_content}
`; return new Response(html, { headers: { "content-type": "text/html;charset=UTF-8" }, }); }, }; ``` Explain Code TypeScript ``` export default { async fetch(request): Promise { let grads = [ [ { color: "00000c", position: 0 }, { color: "00000c", position: 0 }, ], [ { color: "020111", position: 85 }, { color: "191621", position: 100 }, ], [ { color: "020111", position: 60 }, { color: "20202c", position: 100 }, ], [ { color: "020111", position: 10 }, { color: "3a3a52", position: 100 }, ], [ { color: "20202c", position: 0 }, { color: "515175", position: 100 }, ], [ { color: "40405c", position: 0 }, { color: "6f71aa", position: 80 }, { color: "8a76ab", position: 100 }, ], [ { color: "4a4969", position: 0 }, { color: "7072ab", position: 50 }, { color: "cd82a0", position: 100 }, ], [ { color: "757abf", position: 0 }, { color: "8583be", position: 60 }, { color: "eab0d1", position: 100 }, ], [ { color: "82addb", position: 0 }, { color: "ebb2b1", position: 100 }, ], [ { color: "94c5f8", position: 1 }, { color: "a6e6ff", position: 70 }, { color: "b1b5ea", position: 100 }, ], [ { color: "b7eaff", position: 0 }, { color: "94dfff", position: 100 }, ], [ { color: "9be2fe", position: 0 }, { color: "67d1fb", position: 100 }, ], [ { color: "90dffe", position: 0 }, { color: "38a3d1", position: 100 }, ], [ { color: "57c1eb", position: 0 }, { color: "246fa8", position: 100 }, ], [ { color: "2d91c2", position: 0 }, { color: "1e528e", position: 100 }, ], [ { color: "2473ab", position: 0 }, { color: "1e528e", position: 70 }, { color: "5b7983", position: 100 }, ], [ { color: "1e528e", position: 0 }, { color: "265889", position: 50 }, { color: "9da671", position: 100 }, ], [ { color: "1e528e", position: 0 }, { color: "728a7c", position: 50 }, { color: "e9ce5d", position: 100 }, ], [ { color: "154277", position: 0 }, { color: "576e71", position: 30 }, { color: "e1c45e", position: 70 }, { color: "b26339", position: 100 }, ], [ { color: "163C52", position: 0 }, { color: "4F4F47", position: 30 }, { color: "C5752D", position: 60 }, { color: "B7490F", position: 80 }, { color: "2F1107", position: 100 }, ], [ { color: "071B26", position: 0 }, { color: "071B26", position: 30 }, { color: "8A3B12", position: 80 }, { color: "240E03", position: 100 }, ], [ { color: "010A10", position: 30 }, { color: "59230B", position: 80 }, { color: "2F1107", position: 100 }, ], [ { color: "090401", position: 50 }, { color: "4B1D06", position: 100 }, ], [ { color: "00000c", position: 80 }, { color: "150800", position: 100 }, ], ]; async function toCSSGradient(hour) { let css = "linear-gradient(to bottom,"; const data = grads[hour]; const len = data.length; for (let i = 0; i < len; i++) { const item = data[i]; css += ` #${item.color} ${item.position}%`; if (i < len - 1) css += ","; } return css + ")"; } let html_content = ""; let html_style = ` html{width:100vw; height:100vh;} body{padding:0; margin:0 !important;height:100%;} #container { display: flex; flex-direction:column; align-items: center; justify-content: center; height: 100%; color:white; font-family:sans-serif; }`; const timezone = request.cf.timezone; console.log(timezone); let localized_date = new Date( new Date().toLocaleString("en-US", { timeZone: timezone }), ); let hour = localized_date.getHours(); let minutes = localized_date.getMinutes(); html_content += "

" + hour + ":" + minutes + "

"; html_content += "

" + timezone + "

"; html_style += "body{background:" + (await toCSSGradient(hour)) + ";}"; let html = ` Geolocation: Customized Design
${html_content}
`; return new Response(html, { headers: { "content-type": "text/html;charset=UTF-8" }, }); }, } satisfies ExportedHandler; ``` Explain Code TypeScript ``` import { Hono } from 'hono'; type Bindings = {}; type ColorStop = { color: string; position: number }; const app = new Hono<{ Bindings: Bindings }>(); // Gradient configurations for each hour of the day (0-23) const grads: ColorStop[][] = [ [ { color: "00000c", position: 0 }, { color: "00000c", position: 0 }, ], [ { color: "020111", position: 85 }, { color: "191621", position: 100 }, ], [ { color: "020111", position: 60 }, { color: "20202c", position: 100 }, ], [ { color: "020111", position: 10 }, { color: "3a3a52", position: 100 }, ], [ { color: "20202c", position: 0 }, { color: "515175", position: 100 }, ], [ { color: "40405c", position: 0 }, { color: "6f71aa", position: 80 }, { color: "8a76ab", position: 100 }, ], [ { color: "4a4969", position: 0 }, { color: "7072ab", position: 50 }, { color: "cd82a0", position: 100 }, ], [ { color: "757abf", position: 0 }, { color: "8583be", position: 60 }, { color: "eab0d1", position: 100 }, ], [ { color: "82addb", position: 0 }, { color: "ebb2b1", position: 100 }, ], [ { color: "94c5f8", position: 1 }, { color: "a6e6ff", position: 70 }, { color: "b1b5ea", position: 100 }, ], [ { color: "b7eaff", position: 0 }, { color: "94dfff", position: 100 }, ], [ { color: "9be2fe", position: 0 }, { color: "67d1fb", position: 100 }, ], [ { color: "90dffe", position: 0 }, { color: "38a3d1", position: 100 }, ], [ { color: "57c1eb", position: 0 }, { color: "246fa8", position: 100 }, ], [ { color: "2d91c2", position: 0 }, { color: "1e528e", position: 100 }, ], [ { color: "2473ab", position: 0 }, { color: "1e528e", position: 70 }, { color: "5b7983", position: 100 }, ], [ { color: "1e528e", position: 0 }, { color: "265889", position: 50 }, { color: "9da671", position: 100 }, ], [ { color: "1e528e", position: 0 }, { color: "728a7c", position: 50 }, { color: "e9ce5d", position: 100 }, ], [ { color: "154277", position: 0 }, { color: "576e71", position: 30 }, { color: "e1c45e", position: 70 }, { color: "b26339", position: 100 }, ], [ { color: "163C52", position: 0 }, { color: "4F4F47", position: 30 }, { color: "C5752D", position: 60 }, { color: "B7490F", position: 80 }, { color: "2F1107", position: 100 }, ], [ { color: "071B26", position: 0 }, { color: "071B26", position: 30 }, { color: "8A3B12", position: 80 }, { color: "240E03", position: 100 }, ], [ { color: "010A10", position: 30 }, { color: "59230B", position: 80 }, { color: "2F1107", position: 100 }, ], [ { color: "090401", position: 50 }, { color: "4B1D06", position: 100 }, ], [ { color: "00000c", position: 80 }, { color: "150800", position: 100 }, ], ]; // Convert hour to CSS gradient async function toCSSGradient(hour: number): Promise { let css = "linear-gradient(to bottom,"; const data = grads[hour]; const len = data.length; for (let i = 0; i < len; i++) { const item = data[i]; css += ` #${item.color} ${item.position}%`; if (i < len - 1) css += ","; } return css + ")"; } app.get('*', async (c) => { const request = c.req.raw; // Base HTML style let html_style = ` html{width:100vw; height:100vh;} body{padding:0; margin:0 !important;height:100%;} #container { display: flex; flex-direction:column; align-items: center; justify-content: center; height: 100%; color:white; font-family:sans-serif; }`; // Get timezone from Cloudflare request const timezone = request.cf?.timezone || 'UTC'; console.log(timezone); // Get localized time let localized_date = new Date( new Date().toLocaleString("en-US", { timeZone: timezone }) ); let hour = localized_date.getHours(); let minutes = localized_date.getMinutes(); // Generate HTML content let html_content = `

${hour}:${minutes}

`; html_content += `

${timezone}

`; // Add background gradient based on hour html_style += `body{background:${await toCSSGradient(hour)};}`; // Complete HTML document let html = ` Geolocation: Customized Design
${html_content}
`; return c.html(html); }); export default app; ``` Explain Code ```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/geolocation-custom-styling/","name":"Geolocation: Custom Styling"}}]} ``` --- --- title: Geolocation: Hello World description: Get all geolocation data fields and display them in HTML. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) ### Tags [ Geolocation ](https://developers.cloudflare.com/search/?tags=Geolocation)[ JavaScript ](https://developers.cloudflare.com/search/?tags=JavaScript)[ TypeScript ](https://developers.cloudflare.com/search/?tags=TypeScript)[ Python ](https://developers.cloudflare.com/search/?tags=Python) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/examples/geolocation-hello-world.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Geolocation: Hello World **Last reviewed:** about 4 years ago Get all geolocation data fields and display them in HTML. 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/geolocation-hello-world) This creates a repository in your GitHub account and deploys the application to Cloudflare Workers. * [ JavaScript ](#tab-panel-7459) * [ TypeScript ](#tab-panel-7460) * [ Python ](#tab-panel-7461) * [ Hono ](#tab-panel-7462) JavaScript ``` export default { async fetch(request) { let html_content = ""; let html_style = "body{padding:6em; font-family: sans-serif;} h1{color:#f6821f;}"; html_content += "

Colo: " + request.cf.colo + "

"; html_content += "

Country: " + request.cf.country + "

"; html_content += "

City: " + request.cf.city + "

"; html_content += "

Continent: " + request.cf.continent + "

"; html_content += "

Latitude: " + request.cf.latitude + "

"; html_content += "

Longitude: " + request.cf.longitude + "

"; html_content += "

PostalCode: " + request.cf.postalCode + "

"; html_content += "

MetroCode: " + request.cf.metroCode + "

"; html_content += "

Region: " + request.cf.region + "

"; html_content += "

RegionCode: " + request.cf.regionCode + "

"; html_content += "

Timezone: " + request.cf.timezone + "

"; let html = ` Geolocation: Hello World

Geolocation: Hello World!

You now have access to geolocation data about where your user is visiting from.

${html_content} `; return new Response(html, { headers: { "content-type": "text/html;charset=UTF-8", }, }); }, }; ``` Explain Code TypeScript ``` export default { async fetch(request): Promise { let html_content = ""; let html_style = "body{padding:6em; font-family: sans-serif;} h1{color:#f6821f;}"; html_content += "

Colo: " + request.cf.colo + "

"; html_content += "

Country: " + request.cf.country + "

"; html_content += "

City: " + request.cf.city + "

"; html_content += "

Continent: " + request.cf.continent + "

"; html_content += "

Latitude: " + request.cf.latitude + "

"; html_content += "

Longitude: " + request.cf.longitude + "

"; html_content += "

PostalCode: " + request.cf.postalCode + "

"; html_content += "

MetroCode: " + request.cf.metroCode + "

"; html_content += "

Region: " + request.cf.region + "

"; html_content += "

RegionCode: " + request.cf.regionCode + "

"; html_content += "

Timezone: " + request.cf.timezone + "

"; let html = ` Geolocation: Hello World

Geolocation: Hello World!

You now have access to geolocation data about where your user is visiting from.

${html_content} `; return new Response(html, { headers: { "content-type": "text/html;charset=UTF-8", }, }); }, } satisfies ExportedHandler; ``` Explain Code Python ``` from workers import WorkerEntrypoint, Response class Default(WorkerEntrypoint): async def fetch(self, request): html_content = "" html_style = "body{padding:6em font-family: sans-serif;} h1{color:#f6821f;}" html_content += "

Colo: " + request.cf.colo + "

" html_content += "

Country: " + request.cf.country + "

" html_content += "

City: " + request.cf.city + "

" html_content += "

Continent: " + request.cf.continent + "

" html_content += "

Latitude: " + request.cf.latitude + "

" html_content += "

Longitude: " + request.cf.longitude + "

" html_content += "

PostalCode: " + request.cf.postalCode + "

" html_content += "

Region: " + request.cf.region + "

" html_content += "

RegionCode: " + request.cf.regionCode + "

" html_content += "

Timezone: " + request.cf.timezone + "

" html = f""" Geolocation: Hello World

Geolocation: Hello World!

You now have access to geolocation data about where your user is visiting from.

{html_content} """ headers = {"content-type": "text/html;charset=UTF-8"} return Response(html, headers=headers) ``` Explain Code TypeScript ``` import { Hono } from "hono"; import { html } from "hono/html"; // Define the RequestWithCf interface to add Cloudflare-specific properties interface RequestWithCf extends Request { cf: { // Cloudflare-specific properties for geolocation colo: string; country: string; city: string; continent: string; latitude: string; longitude: string; postalCode: string; metroCode: string; region: string; regionCode: string; timezone: string; // Add other CF properties as needed }; } const app = new Hono(); app.get("*", (c) => { // Cast the raw request to include Cloudflare-specific properties const request = c.req.raw; // Define styles const html_style = "body{padding:6em; font-family: sans-serif;} h1{color:#f6821f;}"; // Create content with geolocation data let html_content = html`

Colo: ${request.cf.colo}

Country: ${request.cf.country}

City: ${request.cf.city}

Continent: ${request.cf.continent}

Latitude: ${request.cf.latitude}

Longitude: ${request.cf.longitude}

PostalCode: ${request.cf.postalCode}

MetroCode: ${request.cf.metroCode}

Region: ${request.cf.region}

RegionCode: ${request.cf.regionCode}

Timezone: ${request.cf.timezone}

`; // Compose the full HTML const htmlContent = html` Geolocation: Hello World

Geolocation: Hello World!

You now have access to geolocation data about where your user is visiting from.

${html_content} `; // Return the HTML response return c.html(htmlContent); }); export default app; ``` Explain Code ```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/geolocation-hello-world/","name":"Geolocation: Hello World"}}]} ``` --- --- title: Hot-link protection description: Block other websites from linking to your content. This is useful for protecting images. image: https://developers.cloudflare.com/dev-products-preview.png --- [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) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/examples/hot-link-protection.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Hot-link protection **Last reviewed:** over 5 years ago Block other websites from linking to your content. This is useful for protecting images. 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/hot-link-protection) This creates a repository in your GitHub account and deploys the application to Cloudflare Workers. * [ JavaScript ](#tab-panel-7463) * [ TypeScript ](#tab-panel-7464) * [ Python ](#tab-panel-7465) * [ Hono ](#tab-panel-7466) JavaScript ``` export default { async fetch(request) { const HOMEPAGE_URL = "https://tutorial.cloudflareworkers.com/"; const PROTECTED_TYPE = "image/"; // Fetch the original request const response = await fetch(request); // If it's an image, engage hotlink protection based on the // Referer header. const referer = request.headers.get("Referer"); const contentType = response.headers.get("Content-Type") || ""; if (referer && contentType.startsWith(PROTECTED_TYPE)) { // If the hostnames don't match, it's a hotlink if (new URL(referer).hostname !== new URL(request.url).hostname) { // Redirect the user to your website return Response.redirect(HOMEPAGE_URL, 302); } } // Everything is fine, return the response normally. return response; }, }; ``` Explain Code TypeScript ``` export default { async fetch(request): Promise { const HOMEPAGE_URL = "https://tutorial.cloudflareworkers.com/"; const PROTECTED_TYPE = "image/"; // Fetch the original request const response = await fetch(request); // If it's an image, engage hotlink protection based on the // Referer header. const referer = request.headers.get("Referer"); const contentType = response.headers.get("Content-Type") || ""; if (referer && contentType.startsWith(PROTECTED_TYPE)) { // If the hostnames don't match, it's a hotlink if (new URL(referer).hostname !== new URL(request.url).hostname) { // Redirect the user to your website return Response.redirect(HOMEPAGE_URL, 302); } } // Everything is fine, return the response normally. return response; }, } satisfies ExportedHandler; ``` Explain Code Python ``` from workers import WorkerEntrypoint, Response, fetch from urllib.parse import urlparse class Default(WorkerEntrypoint): async def fetch(self, request): homepage_url = "https://tutorial.cloudflareworkers.com/" protected_type = "image/" # Fetch the original request response = await fetch(request) # If it's an image, engage hotlink protection based on the referer header referer = request.headers["Referer"] content_type = response.headers["Content-Type"] or "" if referer and content_type.startswith(protected_type): # If the hostnames don't match, it's a hotlink if urlparse(referer).hostname != urlparse(request.url).hostname: # Redirect the user to your website return Response.redirect(homepage_url, 302) # Everything is fine, return the response normally return response ``` Explain Code TypeScript ``` import { Hono } from 'hono'; const app = new Hono(); // Middleware for hot-link protection app.use('*', async (c, next) => { const HOMEPAGE_URL = "https://tutorial.cloudflareworkers.com/"; const PROTECTED_TYPE = "image/"; // Continue to the next handler to get the response await next(); // If we have a response, check for hotlinking if (c.res) { // If it's an image, engage hotlink protection based on the Referer header const referer = c.req.header("Referer"); const contentType = c.res.headers.get("Content-Type") || ""; if (referer && contentType.startsWith(PROTECTED_TYPE)) { // If the hostnames don't match, it's a hotlink if (new URL(referer).hostname !== new URL(c.req.url).hostname) { // Redirect the user to your website c.res = c.redirect(HOMEPAGE_URL, 302); } } } }); // Default route handler that passes through the request to the origin app.all('*', async (c) => { // Fetch the original request return fetch(c.req.raw); }); export default app; ``` Explain Code ```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/hot-link-protection/","name":"Hot-link protection"}}]} ``` --- --- title: Custom Domain with Images description: Set up custom domain for Images using a Worker or serve images using a prefix path and Cloudflare registered domain. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) ### Tags [ JavaScript ](https://developers.cloudflare.com/search/?tags=JavaScript)[ TypeScript ](https://developers.cloudflare.com/search/?tags=TypeScript)[ Python ](https://developers.cloudflare.com/search/?tags=Python) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/examples/images-workers.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Custom Domain with Images **Last reviewed:** over 3 years ago Set up custom domain for Images using a Worker or serve images using a prefix path and Cloudflare registered domain. 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/images-workers) This creates a repository in your GitHub account and deploys the application to Cloudflare Workers. To serve images from a custom domain: 1. In the Cloudflare dashboard, go to the **Workers & Pages** page. [ Go to **Workers & Pages** ](https://dash.cloudflare.com/?to=/:account/workers-and-pages) 2. Select **Create application** \> **Workers** \> **Create Worker** and create your Worker. 3. In your Worker, select **Quick edit** and paste the following code. * [ JavaScript ](#tab-panel-7467) * [ TypeScript ](#tab-panel-7468) * [ Hono ](#tab-panel-7469) * [ Python ](#tab-panel-7470) JavaScript ``` export default { async fetch(request) { // You can find this in the dashboard, it should look something like this: ZWd9g1K7eljCn_KDTu_MWA const accountHash = ""; const { pathname } = new URL(request.url); // A request to something like cdn.example.com/83eb7b2-5392-4565-b69e-aff66acddd00/public // will fetch "https://imagedelivery.net//83eb7b2-5392-4565-b69e-aff66acddd00/public" return fetch(`https://imagedelivery.net/${accountHash}${pathname}`); }, }; ``` Explain Code TypeScript ``` export default { async fetch(request): Promise { // You can find this in the dashboard, it should look something like this: ZWd9g1K7eljCn_KDTu_MWA const accountHash = ""; const { pathname } = new URL(request.url); // A request to something like cdn.example.com/83eb7b2-5392-4565-b69e-aff66acddd00/public // will fetch "https://imagedelivery.net//83eb7b2-5392-4565-b69e-aff66acddd00/public" return fetch(`https://imagedelivery.net/${accountHash}${pathname}`); }, } satisfies ExportedHandler; ``` Explain Code TypeScript ``` import { Hono } from 'hono'; interface Env { // You can store your account hash as a binding variable ACCOUNT_HASH?: string; } const app = new Hono<{ Bindings: Env }>(); app.get('*', async (c) => { // You can find this in the dashboard, it should look something like this: ZWd9g1K7eljCn_KDTu_MWA // Either get it from environment or hardcode it here const accountHash = c.env.ACCOUNT_HASH || ""; const url = new URL(c.req.url); // A request to something like cdn.example.com/83eb7b2-5392-4565-b69e-aff66acddd00/public // will fetch "https://imagedelivery.net//83eb7b2-5392-4565-b69e-aff66acddd00/public" return fetch(`https://imagedelivery.net/${accountHash}${url.pathname}`); }); export default app; ``` Explain Code Python ``` from workers import WorkerEntrypoint from js import URL, fetch class Default(WorkerEntrypoint): async def fetch(self, request): # You can find this in the dashboard, it should look something like this: ZWd9g1K7eljCn_KDTu_MWA account_hash = "" url = URL.new(request.url) # A request to something like cdn.example.com/83eb7b2-5392-4565-b69e-aff66acddd00/public # will fetch "https://imagedelivery.net//83eb7b2-5392-4565-b69e-aff66acddd00/public" return fetch(f'https://imagedelivery.net/{account_hash}{url.pathname}') ``` Explain Code Another way you can serve images from a custom domain is by using the `cdn-cgi/imagedelivery` prefix path which is used as path to trigger `cdn-cgi` image proxy. Below is an example showing the hostname as a Cloudflare proxied domain under the same account as the Image, followed with the prefix path and the image ``, `` and `` which can be found in the **Images** on the Cloudflare dashboard. JavaScript ``` https://example.com/cdn-cgi/imagedelivery/// ``` ```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/images-workers/","name":"Custom Domain with Images"}}]} ``` --- --- title: Logging headers to console description: Examine the contents of a Headers object by logging to console with a Map. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) ### Tags [ Debugging ](https://developers.cloudflare.com/search/?tags=Debugging)[ Headers ](https://developers.cloudflare.com/search/?tags=Headers)[ JavaScript ](https://developers.cloudflare.com/search/?tags=JavaScript)[ Rust ](https://developers.cloudflare.com/search/?tags=Rust)[ TypeScript ](https://developers.cloudflare.com/search/?tags=TypeScript)[ Python ](https://developers.cloudflare.com/search/?tags=Python) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/examples/logging-headers.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Logging headers to console **Last reviewed:** over 5 years ago Examine the contents of a Headers object by logging to console with a Map. 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/logging-headers) This creates a repository in your GitHub account and deploys the application to Cloudflare Workers. * [ JavaScript ](#tab-panel-7471) * [ TypeScript ](#tab-panel-7472) * [ Python ](#tab-panel-7473) * [ Rust ](#tab-panel-7474) * [ Hono ](#tab-panel-7475) JavaScript ``` export default { async fetch(request) { console.log(new Map(request.headers)); return new Response("Hello world"); }, }; ``` TypeScript ``` export default { async fetch(request): Promise { console.log(new Map(request.headers)); return new Response("Hello world"); }, } satisfies ExportedHandler; ``` Python ``` from workers import WorkerEntrypoint, Response class Default(WorkerEntrypoint): async def fetch(self, request): print(dict(request.headers)) return Response('Hello world') ``` ``` use worker::*; #[event(fetch)] async fn fetch(req: HttpRequest, _env: Env, _ctx: Context) -> Result { console_log!("{:?}", req.headers()); Response::ok("hello world") } ``` TypeScript ``` import { Hono } from 'hono'; const app = new Hono(); app.get('*', (c) => { // Different ways to log headers in Hono: // 1. Using Map to display headers in console console.log('Headers as Map:', new Map(c.req.raw.headers)); // 2. Using spread operator to log headers console.log('Headers spread:', [...c.req.raw.headers]); // 3. Using Object.fromEntries to convert to an object console.log('Headers as Object:', Object.fromEntries(c.req.raw.headers)); // 4. Hono's built-in header accessor (for individual headers) console.log('User-Agent:', c.req.header('User-Agent')); // 5. Using c.req.headers to get all headers console.log('All headers from Hono context:', c.req.header()); return c.text('Hello world'); }); export default app; ``` Explain Code --- ## Console-logging headers Use a `Map` if you need to log a `Headers` object to the console: JavaScript ``` console.log(new Map(request.headers)); ``` Use the `spread` operator if you need to quickly stringify a `Headers` object: JavaScript ``` let requestHeaders = JSON.stringify([...request.headers]); ``` Use `Object.fromEntries` to convert the headers to an object: JavaScript ``` let requestHeaders = Object.fromEntries(request.headers); ``` ### The problem When debugging Workers, examine the headers on a request or response. A common mistake is to try to log headers to the developer console via code like this: JavaScript ``` console.log(request.headers); ``` Or this: JavaScript ``` console.log(`Request headers: ${JSON.stringify(request.headers)}`); ``` Both attempts result in what appears to be an empty object — the string `"{}"` — even though calling `request.headers.has("Your-Header-Name")` might return true. This is the same behavior that browsers implement. The reason this happens is because [Headers ↗](https://developer.mozilla.org/en-US/docs/Web/API/Headers) objects do not store headers in enumerable JavaScript properties, so the developer console and JSON stringifier do not know how to read the names and values of the headers. It is not actually an empty object, but rather an opaque object. `Headers` objects are iterable, which you can take advantage of to develop a couple of quick one-liners for debug-printing headers. ### Pass headers through a Map The first common idiom for making Headers `console.log()`\-friendly is to construct a `Map` object from the `Headers` object and log the `Map` object. JavaScript ``` console.log(new Map(request.headers)); ``` This works because: * `Map` objects can be constructed from iterables, like `Headers`. * The `Map` object does store its entries in enumerable JavaScript properties, so the developer console can see into it. ### Spread headers into an array The `Map` approach works for calls to `console.log()`. If you need to stringify your headers, you will discover that stringifying a `Map` yields nothing more than `[object Map]`. Even though a `Map` stores its data in enumerable properties, those properties are [Symbol ↗](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global%5FObjects/Symbol)\-keyed. Because of this, `JSON.stringify()` will [ignore Symbol-keyed properties ↗](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global%5FObjects/Symbol#symbols%5Fand%5Fjson.stringify) and you will receive an empty `{}`. Instead, you can take advantage of the iterability of the `Headers` object in a new way by applying the [spread operator ↗](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread%5Fsyntax) (`...`) to it. JavaScript ``` let requestHeaders = JSON.stringify([...request.headers], null, 2); console.log(`Request headers: ${requestHeaders}`); ``` ### Convert headers into an object with Object.fromEntries (ES2019) ES2019 provides [Object.fromEntries ↗](https://github.com/tc39/proposal-object-from-entries) which is a call to convert the headers into an object: JavaScript ``` let headersObject = Object.fromEntries(request.headers); let requestHeaders = JSON.stringify(headersObject, null, 2); console.log(`Request headers: ${requestHeaders}`); ``` This results in something like: JavaScript ``` Request headers: { "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8", "accept-encoding": "gzip", "accept-language": "en-US,en;q=0.9", "cf-ipcountry": "US", // ... }" ``` ```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/logging-headers/","name":"Logging headers to console"}}]} ``` --- --- title: Modify request property description: Create a modified request with edited properties based off of an incoming request. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) ### Tags [ Middleware ](https://developers.cloudflare.com/search/?tags=Middleware)[ 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) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/examples/modify-request-property.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Modify request property **Last reviewed:** over 5 years ago Create a modified request with edited properties based off of an incoming request. 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/modify-request-property) This creates a repository in your GitHub account and deploys the application to Cloudflare Workers. * [ JavaScript ](#tab-panel-7476) * [ TypeScript ](#tab-panel-7477) * [ Python ](#tab-panel-7478) * [ Hono ](#tab-panel-7479) JavaScript ``` export default { async fetch(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"; /** * The best practice is to only assign new RequestInit properties * on the request object 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, }); } }, }; ``` Explain Code TypeScript ``` export default { async fetch(request): Promise { /** * 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"; /** * The best practice is to only assign new RequestInit properties * on the request object 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, }); } }, } satisfies ExportedHandler; ``` Explain Code Python ``` import json from workers import WorkerEntrypoint from pyodide.ffi import to_js as _to_js from js import Object, URL, Request, fetch, Response def to_js(obj): return _to_js(obj, dict_converter=Object.fromEntries) class Default(WorkerEntrypoint): async def fetch(self, request): some_host = "example.com" some_url = "https://foo.example.com/api.js" # The best practice is to only assign new_request_init properties # on the request object using either a method or the constructor new_request_init = { "method": "POST", # Change method "body": json.dumps({ "bar": "foo" }), # Change body "redirect": "follow", # Change the redirect mode # 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 url = URL.new(some_url) url.hostname = some_host # 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. org_request = Request.new(request, new_request_init) new_request = Request.new(url.toString(),org_request) new_request.headers["X-Example"] = "bar" new_request.headers["Content-Type"] = "application/json" try: return await fetch(new_request) except Exception as e: return Response.new({"error": str(e)}, status=500) ``` Explain Code TypeScript ``` import { Hono } from "hono"; const app = new Hono(); app.all("*", async (c) => { /** * Example someHost is set up to return raw JSON */ const someHost = "example.com"; const someUrl = "https://foo.example.com/api.js"; // Create a URL object to modify the hostname const url = new URL(someUrl); url.hostname = someHost; // Create a new request // First create a clone of the original request with the new properties const requestClone = new Request(c.req.raw, { // Change method method: "POST", // Change body body: JSON.stringify({ bar: "foo" }), // Change the redirect mode redirect: "follow" as RequestRedirect, // Change headers, note this method will erase existing headers headers: { "Content-Type": "application/json", "X-Example": "bar", }, // Change a Cloudflare feature on the outbound response cf: { apps: false }, }); // Then create a new request with the modified URL const newRequest = new Request(url.toString(), requestClone); // Send the modified request const response = await fetch(newRequest); // Return the response return response; }); // Handle errors app.onError((err, c) => { return err.getResponse(); }); export default app; ``` Explain Code ```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/modify-request-property/","name":"Modify request property"}}]} ``` --- --- title: Modify response description: Fetch and modify response properties which are immutable by creating a copy first. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) ### Tags [ Middleware ](https://developers.cloudflare.com/search/?tags=Middleware)[ 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) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/examples/modify-response.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Modify response **Last reviewed:** over 5 years ago Fetch and modify response properties which are immutable by creating a copy first. 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/modify-response) This creates a repository in your GitHub account and deploys the application to Cloudflare Workers. * [ JavaScript ](#tab-panel-7480) * [ TypeScript ](#tab-panel-7481) * [ Python ](#tab-panel-7482) * [ Hono ](#tab-panel-7483) JavaScript ``` export default { async fetch(request) { /** * @param {string} headerNameSrc Header to get the new value from * @param {string} headerNameDst Header to set based off of value in src */ const headerNameSrc = "foo"; //"Orig-Header" const headerNameDst = "Last-Modified"; /** * Response properties are immutable. To change them, construct a new * Response and pass modified status or statusText in the ResponseInit * object. Response headers can be modified through the headers `set` method. */ const originalResponse = await fetch(request); // Change status and statusText, but preserve body and headers let response = new Response(originalResponse.body, { status: 500, statusText: "some message", headers: originalResponse.headers, }); // Change response body by adding the foo prop const originalBody = await originalResponse.json(); const body = JSON.stringify({ foo: "bar", ...originalBody }); response = new Response(body, response); // Add a header using set method response.headers.set("foo", "bar"); // Set destination header to the value of the source header const src = response.headers.get(headerNameSrc); if (src != null) { response.headers.set(headerNameDst, src); console.log( `Response header "${headerNameDst}" was set to "${response.headers.get( headerNameDst, )}"`, ); } return response; }, }; ``` Explain Code TypeScript ``` export default { async fetch(request): Promise { /** * @param {string} headerNameSrc Header to get the new value from * @param {string} headerNameDst Header to set based off of value in src */ const headerNameSrc = "foo"; //"Orig-Header" const headerNameDst = "Last-Modified"; /** * Response properties are immutable. To change them, construct a new * Response and pass modified status or statusText in the ResponseInit * object. Response headers can be modified through the headers `set` method. */ const originalResponse = await fetch(request); // Change status and statusText, but preserve body and headers let response = new Response(originalResponse.body, { status: 500, statusText: "some message", headers: originalResponse.headers, }); // Change response body by adding the foo prop const originalBody = await originalResponse.json(); const body = JSON.stringify({ foo: "bar", ...originalBody }); response = new Response(body, response); // Add a header using set method response.headers.set("foo", "bar"); // Set destination header to the value of the source header const src = response.headers.get(headerNameSrc); if (src != null) { response.headers.set(headerNameDst, src); console.log( `Response header "${headerNameDst}" was set to "${response.headers.get( headerNameDst, )}"`, ); } return response; }, } satisfies ExportedHandler; ``` Explain Code Python ``` from workers import WorkerEntrypoint, Response, fetch import json class Default(WorkerEntrypoint): async def fetch(self, request): header_name_src = "foo" # Header to get the new value from header_name_dst = "Last-Modified" # Header to set based off of value in src # Response properties are immutable. To change them, construct a new response original_response = await fetch(request) # Change status and statusText, but preserve body and headers response = Response(original_response.body, status=500, status_text="some message", headers=original_response.headers) # Change response body by adding the foo prop new_body = await original_response.json() new_body["foo"] = "bar" response.replace_body(json.dumps(new_body)) # Add a new header response.headers["foo"] = "bar" # Set destination header to the value of the source header src = response.headers[header_name_src] if src is not None: response.headers[header_name_dst] = src print(f'Response header {header_name_dst} was set to {response.headers[header_name_dst]}') return response ``` Explain Code TypeScript ``` import { Hono } from 'hono'; const app = new Hono(); app.get('*', async (c) => { /** * Header configuration */ const headerNameSrc = "foo"; // Header to get the new value from const headerNameDst = "Last-Modified"; // Header to set based off of value in src /** * Response properties are immutable. With Hono, we can modify the response * by creating custom response objects. */ const originalResponse = await fetch(c.req.raw); // Get the JSON body from the original response const originalBody = await originalResponse.json(); // Modify the body by adding a new property const modifiedBody = { foo: "bar", ...originalBody }; // Create a new custom response with modified status, headers, and body const response = new Response(JSON.stringify(modifiedBody), { status: 500, statusText: "some message", headers: originalResponse.headers, }); // Add a header using set method response.headers.set("foo", "bar"); // Set destination header to the value of the source header const src = response.headers.get(headerNameSrc); if (src != null) { response.headers.set(headerNameDst, src); console.log( `Response header "${headerNameDst}" was set to "${response.headers.get(headerNameDst)}"` ); } return response; }); export default app; ``` Explain Code ```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/modify-response/","name":"Modify response"}}]} ``` --- --- title: Multiple Cron Triggers description: Set multiple Cron Triggers on three different schedules. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) ### Tags [ Middleware ](https://developers.cloudflare.com/search/?tags=Middleware)[ JavaScript ](https://developers.cloudflare.com/search/?tags=JavaScript)[ TypeScript ](https://developers.cloudflare.com/search/?tags=TypeScript) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/examples/multiple-cron-triggers.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Multiple Cron Triggers **Last reviewed:** over 4 years ago Set multiple Cron Triggers on three different schedules. 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/multiple-cron-triggers) This creates a repository in your GitHub account and deploys the application to Cloudflare Workers. * [ JavaScript ](#tab-panel-7484) * [ TypeScript ](#tab-panel-7485) * [ Hono ](#tab-panel-7486) JavaScript ``` export default { async scheduled(event, env, ctx) { // Write code for updating your API switch (event.cron) { case "*/3 * * * *": // Every three minutes await updateAPI(); break; case "*/10 * * * *": // Every ten minutes await updateAPI2(); break; case "*/45 * * * *": // Every forty-five minutes await updateAPI3(); break; } console.log("cron processed"); }, }; ``` Explain Code TypeScript ``` interface Env {} export default { async scheduled( controller: ScheduledController, env: Env, ctx: ExecutionContext, ) { // Write code for updating your API switch (controller.cron) { case "*/3 * * * *": // Every three minutes await updateAPI(); break; case "*/10 * * * *": // Every ten minutes await updateAPI2(); break; case "*/45 * * * *": // Every forty-five minutes await updateAPI3(); break; } console.log("cron processed"); }, }; ``` Explain Code TypeScript ``` import { Hono } from "hono"; interface Env {} // Create Hono app const app = new Hono<{ Bindings: Env }>(); // Regular routes for normal HTTP requests app.get("/", (c) => c.text("Multiple Cron Trigger Example")); // Export both the app and a scheduled function export default { // The Hono app handles regular HTTP requests fetch: app.fetch, // The scheduled function handles Cron triggers async scheduled( controller: ScheduledController, env: Env, ctx: ExecutionContext, ) { // Check which cron schedule triggered this execution switch (controller.cron) { case "*/3 * * * *": // Every three minutes await updateAPI(); break; case "*/10 * * * *": // Every ten minutes await updateAPI2(); break; case "*/45 * * * *": // Every forty-five minutes await updateAPI3(); break; } console.log("cron processed"); }, }; ``` Explain Code ## Test Cron Triggers using Wrangler The recommended way of testing Cron Triggers is using Wrangler. Cron Triggers can be tested using Wrangler by passing in the `--test-scheduled` flag to [wrangler dev](https://developers.cloudflare.com/workers/wrangler/commands/general/#dev). This will expose a `/__scheduled` (or `/cdn-cgi/handler/scheduled` for Python Workers) route which can be used to test using a HTTP request. To simulate different cron patterns, a `cron` query parameter can be passed in. Terminal window ``` npx wrangler dev --test-scheduled curl "http://localhost:8787/__scheduled?cron=*%2F3+*+*+*+*" curl "http://localhost:8787/cdn-cgi/handler/scheduled?cron=*+*+*+*+*" # Python Workers ``` ```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/multiple-cron-triggers/","name":"Multiple Cron Triggers"}}]} ``` --- --- title: Stream OpenAI API Responses description: Use the OpenAI v4 SDK to stream responses from OpenAI. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) ### Tags [ AI ](https://developers.cloudflare.com/search/?tags=AI)[ JavaScript ](https://developers.cloudflare.com/search/?tags=JavaScript)[ TypeScript ](https://developers.cloudflare.com/search/?tags=TypeScript) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/examples/openai-sdk-streaming.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Stream OpenAI API Responses **Last reviewed:** over 2 years ago Use the OpenAI v4 SDK to stream responses from OpenAI. 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/openai-sdk-streaming) This creates a repository in your GitHub account and deploys the application to Cloudflare Workers. In order to run this code, you must install the OpenAI SDK by running `npm i openai`. Note For analytics, caching, rate limiting, and more, you can also send requests like this through Cloudflare's [AI Gateway](https://developers.cloudflare.com/ai-gateway/usage/providers/openai/). * [ TypeScript ](#tab-panel-7487) * [ Hono ](#tab-panel-7488) TypeScript ``` import OpenAI from "openai"; export default { async fetch(request, env, ctx): Promise { const openai = new OpenAI({ apiKey: env.OPENAI_API_KEY, }); // Create a TransformStream to handle streaming data let { readable, writable } = new TransformStream(); let writer = writable.getWriter(); const textEncoder = new TextEncoder(); ctx.waitUntil( (async () => { const stream = await openai.chat.completions.create({ model: "gpt-4o-mini", messages: [{ role: "user", content: "Tell me a story" }], stream: true, }); // loop over the data as it is streamed and write to the writeable for await (const part of stream) { writer.write( textEncoder.encode(part.choices[0]?.delta?.content || ""), ); } writer.close(); })(), ); // Send the readable back to the browser return new Response(readable); }, } satisfies ExportedHandler; ``` Explain Code TypeScript ``` import { Hono } from "hono"; import { streamText } from "hono/streaming"; import OpenAI from "openai"; interface Env { OPENAI_API_KEY: string; } const app = new Hono<{ Bindings: Env }>(); app.get("*", async (c) => { const openai = new OpenAI({ apiKey: c.env.OPENAI_API_KEY, }); const chatStream = await openai.chat.completions.create({ model: "gpt-4o-mini", messages: [{ role: "user", content: "Tell me a story" }], stream: true, }); return streamText(c, async (stream) => { for await (const message of chatStream) { await stream.write(message.choices[0].delta.content || ""); } stream.close(); }); }); export default app; ``` Explain Code ```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/openai-sdk-streaming/","name":"Stream OpenAI API Responses"}}]} ``` --- --- title: Post JSON description: Send a POST request with JSON data. Use to share data with external servers. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) ### Tags [ JSON ](https://developers.cloudflare.com/search/?tags=JSON)[ JavaScript ](https://developers.cloudflare.com/search/?tags=JavaScript)[ TypeScript ](https://developers.cloudflare.com/search/?tags=TypeScript)[ Python ](https://developers.cloudflare.com/search/?tags=Python) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/examples/post-json.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Post JSON **Last reviewed:** about 4 years ago Send a POST request with JSON data. Use to share data with external servers. 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/post-json) This creates a repository in your GitHub account and deploys the application to Cloudflare Workers. * [ JavaScript ](#tab-panel-7489) * [ TypeScript ](#tab-panel-7490) * [ Python ](#tab-panel-7491) * [ Hono ](#tab-panel-7492) JavaScript ``` export default { async fetch(request) { /** * Example someHost is set up to take in a JSON request * Replace url with the host you wish to send requests to * @param {string} url the URL to send the request to * @param {BodyInit} body the JSON data to send in the request */ const someHost = "https://examples.cloudflareworkers.com/demos"; const url = someHost + "/requests/json"; const body = { results: ["default data to send"], errors: null, msg: "I sent this to the fetch", }; /** * gatherResponse awaits and returns a response body as a string. * Use await gatherResponse(..) in an async function to get the response body * @param {Response} response */ async function gatherResponse(response) { const { headers } = response; const contentType = headers.get("content-type") || ""; if (contentType.includes("application/json")) { return JSON.stringify(await response.json()); } else if (contentType.includes("application/text")) { return response.text(); } else if (contentType.includes("text/html")) { return response.text(); } else { return response.text(); } } const init = { body: JSON.stringify(body), method: "POST", headers: { "content-type": "application/json;charset=UTF-8", }, }; const response = await fetch(url, init); const results = await gatherResponse(response); return new Response(results, init); }, }; ``` Explain Code TypeScript ``` export default { async fetch(request): Promise { /** * Example someHost is set up to take in a JSON request * Replace url with the host you wish to send requests to * @param {string} url the URL to send the request to * @param {BodyInit} body the JSON data to send in the request */ const someHost = "https://examples.cloudflareworkers.com/demos"; const url = someHost + "/requests/json"; const body = { results: ["default data to send"], errors: null, msg: "I sent this to the fetch", }; /** * gatherResponse awaits and returns a response body as a string. * Use await gatherResponse(..) in an async function to get the response body * @param {Response} response */ async function gatherResponse(response) { const { headers } = response; const contentType = headers.get("content-type") || ""; if (contentType.includes("application/json")) { return JSON.stringify(await response.json()); } else if (contentType.includes("application/text")) { return response.text(); } else if (contentType.includes("text/html")) { return response.text(); } else { return response.text(); } } const init = { body: JSON.stringify(body), method: "POST", headers: { "content-type": "application/json;charset=UTF-8", }, }; const response = await fetch(url, init); const results = await gatherResponse(response); return new Response(results, init); }, } satisfies ExportedHandler; ``` Explain Code Python ``` import json from workers import WorkerEntrypoint from pyodide.ffi import to_js as _to_js from js import Object, fetch, Response, Headers def to_js(obj): return _to_js(obj, dict_converter=Object.fromEntries) # gather_response returns both content-type & response body as a string async def gather_response(response): headers = response.headers content_type = headers["content-type"] or "" if "application/json" in content_type: return (content_type, json.dumps(dict(await response.json()))) return (content_type, await response.text()) class Default(WorkerEntrypoint): async def fetch(self, _request): url = "https://jsonplaceholder.typicode.com/todos/1" body = { "results": ["default data to send"], "errors": None, "msg": "I sent this to the fetch", } options = { "body": json.dumps(body), "method": "POST", "headers": { "content-type": "application/json;charset=UTF-8", }, } response = await fetch(url, to_js(options)) content_type, result = await gather_response(response) headers = Headers.new({"content-type": content_type}.items()) return Response.new(result, headers=headers) ``` Explain Code TypeScript ``` import { Hono } from 'hono'; const app = new Hono(); app.get('*', async (c) => { /** * Example someHost is set up to take in a JSON request * Replace url with the host you wish to send requests to */ const someHost = "https://examples.cloudflareworkers.com/demos"; const url = someHost + "/requests/json"; const body = { results: ["default data to send"], errors: null, msg: "I sent this to the fetch", }; /** * gatherResponse awaits and returns a response body as a string. * Use await gatherResponse(..) in an async function to get the response body */ async function gatherResponse(response: Response) { const { headers } = response; const contentType = headers.get("content-type") || ""; if (contentType.includes("application/json")) { return { contentType, result: JSON.stringify(await response.json()) }; } else if (contentType.includes("application/text")) { return { contentType, result: await response.text() }; } else if (contentType.includes("text/html")) { return { contentType, result: await response.text() }; } else { return { contentType, result: await response.text() }; } } const init = { body: JSON.stringify(body), method: "POST", headers: { "content-type": "application/json;charset=UTF-8", }, }; const response = await fetch(url, init); const { contentType, result } = await gatherResponse(response); return new Response(result, { headers: { "content-type": contentType, }, }); }); export default app; ``` Explain Code ```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/post-json/","name":"Post JSON"}}]} ``` --- --- title: Using timingSafeEqual description: Protect against timing attacks by safely comparing values using `timingSafeEqual`. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) ### Tags [ Security ](https://developers.cloudflare.com/search/?tags=Security)[ Web Crypto ](https://developers.cloudflare.com/search/?tags=Web%20Crypto)[ TypeScript ](https://developers.cloudflare.com/search/?tags=TypeScript)[ Python ](https://developers.cloudflare.com/search/?tags=Python) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/examples/protect-against-timing-attacks.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Using timingSafeEqual **Last reviewed:** over 2 years ago Protect against timing attacks by safely comparing values using `timingSafeEqual`. 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/protect-against-timing-attacks) This creates a repository in your GitHub account and deploys the application to Cloudflare Workers. The [crypto.subtle.timingSafeEqual](https://developers.cloudflare.com/workers/runtime-apis/web-crypto/#timingsafeequal) function compares two values using a constant-time algorithm. The time taken is independent of the contents of the values. When strings are compared using the equality operator (`==` or `===`), the comparison will end at the first mismatched character. By using `timingSafeEqual`, an attacker would not be able to use timing to find where at which point in the two strings there is a difference. The `timingSafeEqual` function takes two `ArrayBuffer` or `TypedArray` values to compare. These buffers must be of equal length, otherwise an exception is thrown. Note that this function is not constant time with respect to the length of the parameters and also does not guarantee constant time for the surrounding code. Handling of secrets should be taken with care to not introduce timing side channels. Warning Do not return early when the input and secret have different lengths. An early return leaks the length of the secret through response timing. Instead, always perform a constant-time comparison as shown in the examples below — when lengths differ, compare the user input against itself and negate the result so the check still fails but takes the same amount of time. In order to compare two strings, you must use the [TextEncoder](https://developers.cloudflare.com/workers/runtime-apis/encoding/#textencoder) API. * [ TypeScript ](#tab-panel-7493) * [ Python ](#tab-panel-7494) * [ Hono ](#tab-panel-7495) TypeScript ``` interface Environment { MY_SECRET_VALUE?: string; } export default { async fetch(req: Request, env: Environment) { if (!env.MY_SECRET_VALUE) { return new Response("Missing secret binding", { status: 500 }); } const authToken = req.headers.get("Authorization") || ""; const encoder = new TextEncoder(); const userValue = encoder.encode(authToken); const secretValue = encoder.encode(env.MY_SECRET_VALUE); // Do not return early when lengths differ — that leaks the secret's // length through timing. Instead, always perform a constant-time // comparison: when the lengths match compare directly; otherwise // compare the user input against itself (always true) and negate. const lengthsMatch = userValue.byteLength === secretValue.byteLength; const isEqual = lengthsMatch ? crypto.subtle.timingSafeEqual(userValue, secretValue) : !crypto.subtle.timingSafeEqual(userValue, userValue); if (!isEqual) { return new Response("Unauthorized", { status: 401 }); } return new Response("Welcome!"); }, }; ``` Explain Code Python ``` from workers import WorkerEntrypoint, Response from js import TextEncoder, crypto class Default(WorkerEntrypoint): async def fetch(self, request): auth_token = request.headers["Authorization"] or "" secret = self.env.MY_SECRET_VALUE if secret is None: return Response("Missing secret binding", status=500) encoder = TextEncoder.new() user_value = encoder.encode(auth_token) secret_value = encoder.encode(secret) # Do not return early when lengths differ — that leaks the secret's # length through timing. Always perform a constant-time comparison. if user_value.byteLength == secret_value.byteLength: is_equal = crypto.subtle.timingSafeEqual(user_value, secret_value) else: is_equal = not crypto.subtle.timingSafeEqual(user_value, user_value) if not is_equal: return Response("Unauthorized", status=401) return Response("Welcome!") ``` Explain Code TypeScript ``` import { Hono } from 'hono'; interface Environment { Bindings: { MY_SECRET_VALUE?: string; } } const app = new Hono(); // Middleware to handle authentication with timing-safe comparison app.use('*', async (c, next) => { const secret = c.env.MY_SECRET_VALUE; if (!secret) { return c.text("Missing secret binding", 500); } const authToken = c.req.header("Authorization") || ""; const encoder = new TextEncoder(); const userValue = encoder.encode(authToken); const secretValue = encoder.encode(secret); // Do not return early when lengths differ — that leaks the secret's // length through timing. Instead, always perform a constant-time // comparison: when the lengths match compare directly; otherwise // compare the user input against itself (always true) and negate. const lengthsMatch = userValue.byteLength === secretValue.byteLength; const isEqual = lengthsMatch ? crypto.subtle.timingSafeEqual(userValue, secretValue) : !crypto.subtle.timingSafeEqual(userValue, userValue); if (!isEqual) { return c.text("Unauthorized", 401); } // If we got here, the auth token is valid await next(); }); // Protected route app.get('*', (c) => { return c.text("Welcome!"); }); export default app; ``` Explain Code ```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/protect-against-timing-attacks/","name":"Using timingSafeEqual"}}]} ``` --- --- title: Read POST description: Serve an HTML form, then read POST requests. Use also to read JSON or POST data from an incoming request. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) ### Tags [ JSON ](https://developers.cloudflare.com/search/?tags=JSON)[ 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) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/examples/read-post.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Read POST **Last reviewed:** over 5 years ago Serve an HTML form, then read POST requests. Use also to read JSON or POST data from an incoming request. 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/read-post) This creates a repository in your GitHub account and deploys the application to Cloudflare Workers. * [ JavaScript ](#tab-panel-7496) * [ TypeScript ](#tab-panel-7497) * [ Python ](#tab-panel-7498) * [ Rust ](#tab-panel-7499) * [ Hono ](#tab-panel-7500) JavaScript ``` export default { async fetch(request) { /** * rawHtmlResponse returns HTML inputted directly * into the worker script * @param {string} html */ function rawHtmlResponse(html) { return new Response(html, { headers: { "content-type": "text/html;charset=UTF-8", }, }); } /** * 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 contentType = request.headers.get("content-type"); if (contentType.includes("application/json")) { return JSON.stringify(await request.json()); } else if (contentType.includes("application/text")) { return request.text(); } else if (contentType.includes("text/html")) { return 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 { // Perhaps some other type of data was submitted in the form // like an image, or some other binary data. return "a file"; } } const { url } = request; if (url.includes("form")) { return rawHtmlResponse(someForm); } if (request.method === "POST") { const reqBody = await readRequestBody(request); const retBody = `The request body sent in was ${reqBody}`; return new Response(retBody); } else if (request.method === "GET") { return new Response("The request was a GET"); } }, }; ``` Explain Code TypeScript ``` export default { async fetch(request): Promise { /** * rawHtmlResponse returns HTML inputted directly * into the worker script * @param {string} html */ function rawHtmlResponse(html) { return new Response(html, { headers: { "content-type": "text/html;charset=UTF-8", }, }); } /** * 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: Request) { const contentType = request.headers.get("content-type"); if (contentType.includes("application/json")) { return JSON.stringify(await request.json()); } else if (contentType.includes("application/text")) { return request.text(); } else if (contentType.includes("text/html")) { return 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 { // Perhaps some other type of data was submitted in the form // like an image, or some other binary data. return "a file"; } } const { url } = request; if (url.includes("form")) { return rawHtmlResponse(someForm); } if (request.method === "POST") { const reqBody = await readRequestBody(request); const retBody = `The request body sent in was ${reqBody}`; return new Response(retBody); } else if (request.method === "GET") { return new Response("The request was a GET"); } }, } satisfies ExportedHandler; ``` Explain Code Python ``` from workers import WorkerEntrypoint from js import Object, Response, Headers, JSON async def read_request_body(request): headers = request.headers content_type = headers["content-type"] or "" if "application/json" in content_type: return JSON.stringify(await request.json()) if "form" in content_type: form = await request.formData() data = Object.fromEntries(form.entries()) return JSON.stringify(data) return await request.text() class Default(WorkerEntrypoint): async def fetch(self, request): def raw_html_response(html): headers = Headers.new({"content-type": "text/html;charset=UTF-8"}.items()) return Response.new(html, headers=headers) if "form" in request.url: return raw_html_response("") if "POST" in request.method: req_body = await read_request_body(request) ret_body = f"The request body sent in was {req_body}" return Response.new(ret_body) return Response.new("The request was not POST") ``` Explain Code ``` use serde::{Deserialize, Serialize}; use worker::*; fn raw_html_response(html: &str) -> Result { Response::from_html(html) } #[derive(Deserialize, Serialize, Debug)] struct Payload { msg: String, } async fn read_request_body(mut req: Request) -> String { let ctype = req.headers().get("content-type").unwrap().unwrap(); match ctype.as_str() { "application/json" => format!("{:?}", req.json::().await.unwrap()), "text/html" => req.text().await.unwrap(), "multipart/form-data" => format!("{:?}", req.form_data().await.unwrap()), _ => String::from("a file"), } } #[event(fetch)] async fn fetch(req: Request, _env: Env, _ctx: Context) -> Result { if String::from(req.url()?).contains("form") { return raw_html_response("some html form"); } match req.method() { Method::Post => { let req_body = read_request_body(req).await; Response::ok(format!("The request body sent in was {}", req_body)) } _ => Response::ok(format!("The result was a {:?}", req.method())), } } ``` Explain Code TypeScript ``` import { Hono } from "hono"; import { html } from "hono/html"; const app = new Hono(); /** * readRequestBody reads in the incoming request body * @param {Request} request the incoming request to read from */ async function readRequestBody(request: Request): Promise { const contentType = request.headers.get("content-type") || ""; if (contentType.includes("application/json")) { const body = await request.json(); return JSON.stringify(body); } else if (contentType.includes("application/text")) { return request.text(); } else if (contentType.includes("text/html")) { return request.text(); } else if (contentType.includes("form")) { const formData = await request.formData(); const body: Record = {}; for (const [key, value] of formData.entries()) { body[key] = value.toString(); } return JSON.stringify(body); } else { // Perhaps some other type of data was submitted in the form // like an image, or some other binary data. return "a file"; } } const someForm = html`
`; app.get("*", async (c) => { const url = c.req.url; if (url.includes("form")) { return c.html(someForm); } return c.text("The request was a GET"); }); app.post("*", async (c) => { const reqBody = await readRequestBody(c.req.raw); const retBody = `The request body sent in was ${reqBody}`; return c.text(retBody); }); export default app; ``` Explain Code Prevent potential errors when accessing request.body The body of a [Request ↗](https://developer.mozilla.org/en-US/docs/Web/API/Request) can only be accessed once. If you previously used `request.formData()` in the same request, you may encounter a TypeError when attempting to access `request.body`. To avoid errors, create a clone of the Request object with `request.clone()` for each subsequent attempt to access a Request's body. Keep in mind that Workers have a [memory limit of 128 MB per Worker](https://developers.cloudflare.com/workers/platform/limits/#memory) and loading particularly large files into a Worker's memory multiple times may reach this limit. To ensure memory usage does not reach this limit, consider using [Streams](https://developers.cloudflare.com/workers/runtime-apis/streams/). ```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/read-post/","name":"Read POST"}}]} ``` --- --- title: Redirect description: Redirect requests from one URL to another or from one set of URLs to another set. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) ### Tags [ Middleware ](https://developers.cloudflare.com/search/?tags=Middleware)[ Redirects ](https://developers.cloudflare.com/search/?tags=Redirects)[ 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) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/examples/redirect.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Redirect **Last reviewed:** about 4 years ago Redirect requests from one URL to another or from one set of URLs to another set. 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/redirect) This creates a repository in your GitHub account and deploys the application to Cloudflare Workers. ## Redirect all requests to one URL * [ JavaScript ](#tab-panel-7506) * [ TypeScript ](#tab-panel-7507) * [ Python ](#tab-panel-7508) * [ Rust ](#tab-panel-7509) * [ Hono ](#tab-panel-7510) JavaScript ``` export default { async fetch(request) { const destinationURL = "https://example.com"; const statusCode = 301; return Response.redirect(destinationURL, statusCode); }, }; ``` [Run Worker in Playground](https://workers.cloudflare.com/playground#LYVwNgLglgDghgJwgegGYHsHALQBM4RwDcABAEbogB2+CAngLzbPYZb6HbW5QDGU2AAwBWAMwAmAOwBGSQA5powQDYAXCxZtgHOFxp8BIiTPmKVAWABQAYXRUIAU3vYAIlADOMdO6jQ7qki08AmISKjhgBwYAIigaBwAPADoAK3do0lQoMCcIqNj45LToq1t7JwhsABU6GAcAuBgYMD4CKDtkFLgANzh3XgRYCABqYHRccAcrK0SvJBJcB1Q4cAgSAG9LEhI+uipeQIcIXgALAAoEBwBHEAd3CABKDa3tkl47e4W76HC-KgBVABKABkSAwSNEThAIDB3KpkMhEhFmg4ku9gBkXtt3lRPvcCCB3LZFmCSEppEQsSRLhAQAgqCRAXcvLjUZceJdeBAzot7nE2nYgcCADQkfG0onjBwPSnbAC+wsscqIVg0zC0Oj0PH4QjEUlkCiUylKdkczjcnm8vnaVACQR0pHCkRikUIOgygWyuWd0TIYHQZBKNlNFWqtXqOyaLV4AqonXcdmmlnW0WAcDiAH0xhMctFVAVFkV0nLVWqNSEtQZdcYDWZlMwrEA) TypeScript ``` export default { async fetch(request): Promise { const destinationURL = "https://example.com"; const statusCode = 301; return Response.redirect(destinationURL, statusCode); }, } satisfies ExportedHandler; ``` Python ``` from workers import WorkerEntrypoint, Response class Default(WorkerEntrypoint): def fetch(self, request): destinationURL = "https://example.com" statusCode = 301 return Response.redirect(destinationURL, statusCode) ``` ``` use worker::*; #[event(fetch)] async fn fetch(_req: Request, _env: Env, _ctx: Context) -> Result { let destination_url = Url::parse("https://example.com")?; let status_code = 301; Response::redirect_with_status(destination_url, status_code) } ``` TypeScript ``` import { Hono } from "hono"; const app = new Hono(); app.all("*", (c) => { const destinationURL = "https://example.com"; const statusCode = 301; return c.redirect(destinationURL, statusCode); }); export default app; ``` Explain Code ## Redirect requests from one domain to another * [ JavaScript ](#tab-panel-7501) * [ TypeScript ](#tab-panel-7502) * [ Python ](#tab-panel-7503) * [ Rust ](#tab-panel-7504) * [ Hono ](#tab-panel-7505) JavaScript ``` export default { async fetch(request) { const base = "https://example.com"; const statusCode = 301; const url = new URL(request.url); const { pathname, search } = url; const destinationURL = `${base}${pathname}${search}`; console.log(destinationURL); return Response.redirect(destinationURL, statusCode); }, }; ``` Explain Code TypeScript ``` export default { async fetch(request): Promise { const base = "https://example.com"; const statusCode = 301; const url = new URL(request.url); const { pathname, search } = url; const destinationURL = `${base}${pathname}${search}`; console.log(destinationURL); return Response.redirect(destinationURL, statusCode); }, } satisfies ExportedHandler; ``` Explain Code Python ``` from workers import WorkerEntrypoint, Response from urllib.parse import urlparse class Default(WorkerEntrypoint): async def fetch(self, request): base = "https://example.com" statusCode = 301 url = urlparse(request.url) destinationURL = f'{base}{url.path}{url.query}' print(destinationURL) return Response.redirect(destinationURL, statusCode) ``` Explain Code ``` use worker::*; #[event(fetch)] async fn fetch(req: Request, _env: Env, _ctx: Context) -> Result { let mut base = Url::parse("https://example.com")?; let status_code = 301; let url = req.url()?; base.set_path(url.path()); base.set_query(url.query()); console_log!("{:?}", base.to_string()); Response::redirect_with_status(base, status_code) } ``` Explain Code TypeScript ``` import { Hono } from "hono"; const app = new Hono(); app.all("*", (c) => { const base = "https://example.com"; const statusCode = 301; const { pathname, search } = new URL(c.req.url); const destinationURL = `${base}${pathname}${search}`; console.log(destinationURL); return c.redirect(destinationURL, statusCode); }); export default app; ``` Explain Code ```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/redirect/","name":"Redirect"}}]} ``` --- --- title: Respond with another site description: Respond to the Worker request with the response from another website (example.com in this example). image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) ### Tags [ Middleware ](https://developers.cloudflare.com/search/?tags=Middleware)[ JavaScript ](https://developers.cloudflare.com/search/?tags=JavaScript)[ TypeScript ](https://developers.cloudflare.com/search/?tags=TypeScript)[ Python ](https://developers.cloudflare.com/search/?tags=Python) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/examples/respond-with-another-site.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Respond with another site **Last reviewed:** over 5 years ago Respond to the Worker request with the response from another website (example.com in this example). 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/respond-with-another-site) This creates a repository in your GitHub account and deploys the application to Cloudflare Workers. * [ JavaScript ](#tab-panel-7511) * [ TypeScript ](#tab-panel-7512) * [ Python ](#tab-panel-7513) JavaScript ``` export default { async fetch(request) { function MethodNotAllowed(request) { return new Response(`Method ${request.method} not allowed.`, { status: 405, headers: { Allow: "GET", }, }); } // Only GET requests work with this proxy. if (request.method !== "GET") return MethodNotAllowed(request); return fetch(`https://example.com`); }, }; ``` [Run Worker in Playground](https://workers.cloudflare.com/playground#LYVwNgLglgDghgJwgegGYHsHALQBM4RwDcABAEbogB2+CAngLzbPYZb6HbW5QDGU2AAwAmAGzCAHAEYAzABZhciRIBcLFm2Ac4XGnwEjx0+YuUBYAFABhdFQgBTO9gAiUAM4x0bqNFsqSmngExCRUcMD2DABEUDT2AB4AdABWblGkqFBgjuGRMXFJqVGWNnaOENgAKnQw9v5wMDBgfARQtsjJcABucG68CLAQANTA6Ljg9paWCZ5IJLj2qHDgECQA3hYkJL10VLwB9hC8ABYAFAj2AI4g9m4QAJTrm1sB1Ly+VCQAsofHYwBy6AgAEEwGB0AB3ey4c5XG53R4bF4vC4QEAIT5UewQkgAJVuniobnspwABj8IH9cCQACRrC7XW4QRIRSljAC+oSB2zBkOhiVJABonsjkXcCCA3P45IIAKyC56ikjHexwBYIKUipUvUHgiH+KIAcQAopUogrtSR2RbRez7kRFVbHchkCQAPJUMB0EgmyokBnwiBuEgQzAAaxDPmOJEp7hIMAQ6HidESjqgqBIsMZdxZvzGJAAhAwGCQjaaoo9UejPhSqYCQbyoTCA0z7Y6qxiDkczqTjhAIDApS6EuEmvZErx0MBSW2ttaLOyiJZ1MxNNpdDx+EIxJJZAolBISrYHE5XB4vD42lR-IFtKQwhFoqy1cF0gEsjlH1EyOCyMVrMe5RVDUdTbI0zS8K07SpLYUwWGsUTAHAsQAPqjOM2RRCo+QLIUaTssuK5rsEG76NuRh7qYEjMJYQA) TypeScript ``` export default { async fetch(request): Promise { function MethodNotAllowed(request) { return new Response(`Method ${request.method} not allowed.`, { status: 405, headers: { Allow: "GET", }, }); } // Only GET requests work with this proxy. if (request.method !== "GET") return MethodNotAllowed(request); return fetch(`https://example.com`); }, } satisfies ExportedHandler; ``` Explain Code Python ``` from workers import WorkerEntrypoint, Response, fetch class Default(WorkerEntrypoint): def fetch(self, request): def method_not_allowed(request): msg = f'Method {request.method} not allowed.' headers = {"Allow": "GET"} return Response(msg, headers=headers, status=405) # Only GET requests work with this proxy. if request.method != "GET": return method_not_allowed(request) return fetch("https://example.com") ``` Explain Code ```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/respond-with-another-site/","name":"Respond with another site"}}]} ``` --- --- title: Return small HTML page description: Deliver an HTML page from an HTML string directly inside the Worker script. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) ### Tags [ 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) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/examples/return-html.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Return small HTML page **Last reviewed:** over 2 years ago Deliver an HTML page from an HTML string directly inside the Worker script. 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/return-html) This creates a repository in your GitHub account and deploys the application to Cloudflare Workers. * [ JavaScript ](#tab-panel-7514) * [ TypeScript ](#tab-panel-7515) * [ Python ](#tab-panel-7516) * [ Rust ](#tab-panel-7517) * [ Hono ](#tab-panel-7518) JavaScript ``` export default { async fetch(request) { const html = `

Hello World

This markup was generated by a Cloudflare Worker.

`; return new Response(html, { headers: { "content-type": "text/html;charset=UTF-8", }, }); }, }; ``` [Run Worker in Playground](https://workers.cloudflare.com/playground#LYVwNgLglgDghgJwgegGYHsHALQBM4RwDcABAEbogB2+CAngLzbPYZb6HbW5QDGU2AAwA2AMyDBARkEAmAJzCArAA4AXCxZtgHOFxp8BI8VNkKVAWABQAYXRUIAU3vYAIlADOMdO6jQ7qki08AmISKjhgBwYAIigaBwAPADoAK3do0lQoMCcIqNj45LToq1t7JwhsABU6GAcAuBgYMD4CKDtkFLgANzh3XgRYCABqYHRccAcrK0SvJBJcB1Q4cAgSAG9LEhI+uipeQIcIXgALAAoEBwBHEAd3CABKDa3tkl47e5ITiGAwEgYSAADAA8AEIXAB5axVACaAAUAKJfH5gAB8L22wIouDo6Ner2BJ0kqIAEg4wGB0CQAOqYMC4YHIIl4-EkYEwVFVE4eEjARAAaxAMBIAHc+iQAOZOBwIAgOXDkOg7EjWSkgXCoMCIBw0zD8mVJRkcjFs5DY3GAoiWE2XCAgBBUMIOEUkABKdy8VHcDjO31+ABpnqyvg44IsEO4Aptg9tou9ys4ILUHNEAtFHAkUH6wERTohvRAGABVKoAMWwymi-pN2wAvtX8bWHla69Xa0QrBpmFodHoePwhGIJNJ5EplKU7I5nG5PN5fO0qAEgjpSOFIjFIoQdBlAtlcuvomRKWQSjZJxVqsmGk0Wrw2h00nZppZ1tE+XEAPpjCY5VMFRZFOktadl2PYhH2BiDsYI5mMozBWEAA) TypeScript ``` export default { async fetch(request): Promise { const html = `

Hello World

This markup was generated by a Cloudflare Worker.

`; return new Response(html, { headers: { "content-type": "text/html;charset=UTF-8", }, }); }, } satisfies ExportedHandler; ``` Explain Code Python ``` from workers import WorkerEntrypoint, Response class Default(WorkerEntrypoint): async def fetch(self, request): html = """

Hello World

This markup was generated by a Cloudflare Worker.

""" headers = {"content-type": "text/html;charset=UTF-8"} return Response(html, headers=headers) ``` Explain Code ``` use worker::*; #[event(fetch)] async fn fetch(_req: Request, _env: Env, _ctx: Context) -> Result { let html = r#"

Hello World

This markup was generated by a Cloudflare Worker.

"#; Response::from_html(html) } ``` Explain Code TypeScript ``` import { Hono } from "hono"; import { html } from "hono/html"; const app = new Hono(); app.get("*", (c) => { const doc = html`

Hello World

This markup was generated by a Cloudflare Worker with Hono.

`; return c.html(doc); }); export default app; ``` Explain Code ```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/return-html/","name":"Return small HTML page"}}]} ``` --- --- title: Return JSON description: Return JSON directly from a Worker script, useful for building APIs and middleware. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) ### Tags [ JSON ](https://developers.cloudflare.com/search/?tags=JSON)[ 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) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/examples/return-json.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Return JSON **Last reviewed:** over 2 years ago Return JSON directly from a Worker script, useful for building APIs and middleware. 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/return-json) This creates a repository in your GitHub account and deploys the application to Cloudflare Workers. * [ JavaScript ](#tab-panel-7519) * [ TypeScript ](#tab-panel-7520) * [ Python ](#tab-panel-7521) * [ Rust ](#tab-panel-7522) * [ Hono ](#tab-panel-7523) JavaScript ``` export default { async fetch(request) { const data = { hello: "world", }; return Response.json(data); }, }; ``` [Run Worker in Playground](https://workers.cloudflare.com/playground#LYVwNgLglgDghgJwgegGYHsHALQBM4RwDcABAEbogB2+CAngLzbPYZb6HbW5QDGU2AAwBOAKwA2ACzDhAJkkAOAMxKAXCxZtgHOFxp8BIidLmKVAWABQAYXRUIAU3vYAIlADOMdO6jQ7qki08AmISKjhgBwYAIigaBwAPADoAK3do0lQoMCcIqNj45LToq1t7JwhsABU6GAcAuBgYMD4CKDtkFLgANzh3XgRYCABqYHRccAcrK0SvJBJcB1Q4cAgSAG9LEhI+uipeQIcIXgALAAoEBwBHEAd3CABKDa3tkl47e4WQkgZn19eTg4wGB0AFogB3TBgXDRAA0L22AF8iJYESRLhAQAgqCQAEp3LxUdwOVLuOxnHQPFFI+HIqwaZhaHR6Hj8IRiKQyeTKJSlOyOZxuTzeXztKgBII6UjhSIxSKEHQZQLZXKy6JkEFkEo2fkVaq1eo7JotXhtDppOzTSzraLAOBxAD6YwmOWiqgKiyK6UR9IZTJCLIM7OMXLMSmYViAA) TypeScript ``` export default { async fetch(request): Promise { const data = { hello: "world", }; return Response.json(data); }, } satisfies ExportedHandler; ``` Python ``` from workers import WorkerEntrypoint, Response import json class Default(WorkerEntrypoint): def fetch(self, request): data = json.dumps({"hello": "world"}) headers = {"content-type": "application/json"} return Response(data, headers=headers) ``` ``` use serde::{Deserialize, Serialize}; use worker::*; #[derive(Deserialize, Serialize, Debug)] struct Json { hello: String, } #[event(fetch)] async fn fetch(_req: Request, _env: Env, _ctx: Context) -> Result { let data = Json { hello: String::from("world"), }; Response::from_json(&data) } ``` Explain Code TypeScript ``` import { Hono } from "hono"; const app = new Hono(); app.get("*", (c) => { const data = { hello: "world", }; return c.json(data); }); export default app; ``` Explain Code ```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/return-json/","name":"Return JSON"}}]} ``` --- --- title: Rewrite links description: Rewrite URL links in HTML using the HTMLRewriter. This is useful for JAMstack websites. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) ### Tags [ JavaScript ](https://developers.cloudflare.com/search/?tags=JavaScript)[ TypeScript ](https://developers.cloudflare.com/search/?tags=TypeScript)[ Python ](https://developers.cloudflare.com/search/?tags=Python) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/examples/rewrite-links.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Rewrite links **Last reviewed:** about 4 years ago Rewrite URL links in HTML using the HTMLRewriter. This is useful for JAMstack websites. 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/rewrite-links) This creates a repository in your GitHub account and deploys the application to Cloudflare Workers. * [ JavaScript ](#tab-panel-7524) * [ TypeScript ](#tab-panel-7525) * [ Python ](#tab-panel-7526) * [ Hono ](#tab-panel-7527) JavaScript ``` export default { async fetch(request) { const OLD_URL = "developer.mozilla.org"; const NEW_URL = "mynewdomain.com"; class AttributeRewriter { constructor(attributeName) { this.attributeName = attributeName; } element(element) { const attribute = element.getAttribute(this.attributeName); if (attribute) { element.setAttribute( this.attributeName, attribute.replace(OLD_URL, NEW_URL), ); } } } const rewriter = new HTMLRewriter() .on("a", new AttributeRewriter("href")) .on("img", new AttributeRewriter("src")); const res = await fetch(request); const contentType = res.headers.get("Content-Type"); // If the response is HTML, it can be transformed with // HTMLRewriter -- otherwise, it should pass through if (contentType.startsWith("text/html")) { return rewriter.transform(res); } else { return res; } }, }; ``` Explain Code TypeScript ``` export default { async fetch(request): Promise { const OLD_URL = "developer.mozilla.org"; const NEW_URL = "mynewdomain.com"; class AttributeRewriter { constructor(attributeName) { this.attributeName = attributeName; } element(element) { const attribute = element.getAttribute(this.attributeName); if (attribute) { element.setAttribute( this.attributeName, attribute.replace(OLD_URL, NEW_URL), ); } } } const rewriter = new HTMLRewriter() .on("a", new AttributeRewriter("href")) .on("img", new AttributeRewriter("src")); const res = await fetch(request); const contentType = res.headers.get("Content-Type"); // If the response is HTML, it can be transformed with // HTMLRewriter -- otherwise, it should pass through if (contentType.startsWith("text/html")) { return rewriter.transform(res); } else { return res; } }, } satisfies ExportedHandler; ``` Explain Code Python ``` from workers import WorkerEntrypoint from pyodide.ffi import create_proxy from js import HTMLRewriter, fetch class AttributeRewriter: old_url = "developer.mozilla.org" new_url = "mynewdomain.com" def __init__(self, attr_name): self.attr_name = attr_name def element(self, element): attr = element.getAttribute(self.attr_name) if attr: element.setAttribute( self.attr_name, attr.replace(self.old_url, self.new_url) ) href = create_proxy(AttributeRewriter("href")) src = create_proxy(AttributeRewriter("src")) rewriter = HTMLRewriter.new().on("a", href).on("img", src) class Default(WorkerEntrypoint): async def fetch(self, request): res = await fetch(request) content_type = res.headers["Content-Type"] # If the response is HTML, it can be transformed with # HTMLRewriter -- otherwise, it should pass through if content_type.startswith("text/html"): return rewriter.transform(res) return res ``` Explain Code TypeScript ``` import { Hono } from 'hono'; import { html } from 'hono/html'; const app = new Hono(); app.get('*', async (c) => { const OLD_URL = "developer.mozilla.org"; const NEW_URL = "mynewdomain.com"; class AttributeRewriter { attributeName: string; constructor(attributeName: string) { this.attributeName = attributeName; } element(element: Element) { const attribute = element.getAttribute(this.attributeName); if (attribute) { element.setAttribute( this.attributeName, attribute.replace(OLD_URL, NEW_URL) ); } } } // Make a fetch request using the original request const res = await fetch(c.req.raw); const contentType = res.headers.get("Content-Type") || ""; // If the response is HTML, transform it with HTMLRewriter if (contentType.startsWith("text/html")) { const rewriter = new HTMLRewriter() .on("a", new AttributeRewriter("href")) .on("img", new AttributeRewriter("src")); return new Response(rewriter.transform(res).body, { headers: res.headers }); } else { // Pass through the response as is return res; } }); export default app; ``` Explain Code ```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/rewrite-links/","name":"Rewrite links"}}]} ``` --- --- title: Set security headers description: Set common security headers (X-XSS-Protection, X-Frame-Options, X-Content-Type-Options, Permissions-Policy, Referrer-Policy, Strict-Transport-Security, Content-Security-Policy). image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) ### Tags [ Security ](https://developers.cloudflare.com/search/?tags=Security)[ Middleware ](https://developers.cloudflare.com/search/?tags=Middleware)[ 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) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/examples/security-headers.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Set security headers **Last reviewed:** about 4 years ago Set common security headers (X-XSS-Protection, X-Frame-Options, X-Content-Type-Options, Permissions-Policy, Referrer-Policy, Strict-Transport-Security, Content-Security-Policy). 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/security-headers) This creates a repository in your GitHub account and deploys the application to Cloudflare Workers. To inject CSP nonces into inline ``, { html: true }, ); } }, }) .transform(shell); }, }; async function fetchBootstrapData(env, pathname, headers) { try { const res = await fetch(`${env.API_BASE_URL}/api/bootstrap`, { headers: { Cookie: headers.get("Cookie") || "", "X-Request-Path": pathname, }, }); if (!res.ok) return null; return await res.json(); } catch { // If the API is down, the shell still loads and the SPA // falls back to client-side data fetching. return null; } } ``` Explain Code TypeScript ``` // Env is generated by `wrangler types` — run it whenever you change your config. // Do not manually define Env — it drifts from your actual bindings. export default { async fetch(request: Request, env: Env): Promise { const url = new URL(request.url); // Serve root-level static files (favicon.ico, robots.txt) directly. // Hashed assets under /assets/* skip the Worker entirely via run_worker_first. if (url.pathname.match(/\.\w+$/) && !url.pathname.endsWith(".html")) { return env.ASSETS.fetch(request); } // Start fetching bootstrap data immediately — do not await yet. const dataPromise = fetchBootstrapData(env, url.pathname, request.headers); // Fetch the SPA shell from static assets (co-located, sub-millisecond). const shell = await env.ASSETS.fetch( new Request(new URL("/index.html", request.url)), ); // Use HTMLRewriter to stream the shell and inject data into . return new HTMLRewriter() .on("body", { async element(el) { const data = await dataPromise; if (data) { el.prepend( ``, { html: true }, ); } }, }) .transform(shell); }, } satisfies ExportedHandler; async function fetchBootstrapData( env: Env, pathname: string, headers: Headers, ): Promise { try { const res = await fetch(`${env.API_BASE_URL}/api/bootstrap`, { headers: { Cookie: headers.get("Cookie") || "", "X-Request-Path": pathname, }, }); if (!res.ok) return null; return await res.json(); } catch { // If the API is down, the shell still loads and the SPA // falls back to client-side data fetching. return null; } } ``` Explain Code --- ## Option 2: SPA hosted on an external origin Use this variant when your HTML, CSS, and JavaScript are deployed outside Cloudflare. The Worker fetches the SPA shell from the external origin, uses HTMLRewriter to inject bootstrap data, and streams the modified response to the browser. ### Configure the Worker Because the SPA is not in Workers Static Assets, you do not need an `assets` block. Instead, store the external origin URL as an environment variable. Attach the Worker to your domain with a [Custom Domain](https://developers.cloudflare.com/workers/configuration/routing/custom-domains/) or a [Route](https://developers.cloudflare.com/workers/configuration/routing/routes/). * [ wrangler.jsonc ](#tab-panel-7539) * [ wrangler.toml ](#tab-panel-7540) JSONC ``` { "name": "my-spa-proxy", "main": "src/worker.ts", // Set this to today's date "compatibility_date": "2026-04-14", "compatibility_flags": ["nodejs_compat"], "vars": { "SPA_ORIGIN": "https://my-spa.example-hosting.com", "API_BASE_URL": "https://api.example.com", }, } ``` Explain Code TOML ``` name = "my-spa-proxy" main = "src/worker.ts" # Set this to today's date compatibility_date = "2026-04-14" compatibility_flags = [ "nodejs_compat" ] [vars] SPA_ORIGIN = "https://my-spa.example-hosting.com" API_BASE_URL = "https://api.example.com" ``` ### Inject bootstrap data with HTMLRewriter The Worker fetches both the SPA shell and API data in parallel. When the SPA origin responds, HTMLRewriter streams the HTML while injecting bootstrap data into ``. Static assets (CSS, JS, images) are passed through to the external origin without modification. * [ JavaScript ](#tab-panel-7543) * [ TypeScript ](#tab-panel-7544) JavaScript ``` // Env is generated by `wrangler types` — run it whenever you change your config. // Do not manually define Env — it drifts from your actual bindings. export default { async fetch(request, env) { const url = new URL(request.url); // Pass static asset requests through to the external origin unmodified. if (url.pathname.match(/\.\w+$/) && !url.pathname.endsWith(".html")) { return fetch(new Request(`${env.SPA_ORIGIN}${url.pathname}`, request)); } // Start fetching bootstrap data immediately — do not await yet. const dataPromise = fetchBootstrapData(env, url.pathname, request.headers); // Fetch the SPA shell from the external origin. // SPA routers serve index.html for all routes. const shell = await fetch(`${env.SPA_ORIGIN}/index.html`); if (!shell.ok) { return new Response("Origin returned an error", { status: 502 }); } // Use HTMLRewriter to stream the shell and inject data into . return new HTMLRewriter() .on("body", { async element(el) { const data = await dataPromise; if (data) { el.prepend( ``, { html: true }, ); } }, }) .transform(shell); }, }; async function fetchBootstrapData(env, pathname, headers) { try { const res = await fetch(`${env.API_BASE_URL}/api/bootstrap`, { headers: { Cookie: headers.get("Cookie") || "", "X-Request-Path": pathname, }, }); if (!res.ok) return null; return await res.json(); } catch { // If the API is down, the shell still loads and the SPA // falls back to client-side data fetching. return null; } } ``` Explain Code TypeScript ``` // Env is generated by `wrangler types` — run it whenever you change your config. // Do not manually define Env — it drifts from your actual bindings. export default { async fetch(request: Request, env: Env): Promise { const url = new URL(request.url); // Pass static asset requests through to the external origin unmodified. if (url.pathname.match(/\.\w+$/) && !url.pathname.endsWith(".html")) { return fetch(new Request(`${env.SPA_ORIGIN}${url.pathname}`, request)); } // Start fetching bootstrap data immediately — do not await yet. const dataPromise = fetchBootstrapData(env, url.pathname, request.headers); // Fetch the SPA shell from the external origin. // SPA routers serve index.html for all routes. const shell = await fetch(`${env.SPA_ORIGIN}/index.html`); if (!shell.ok) { return new Response("Origin returned an error", { status: 502 }); } // Use HTMLRewriter to stream the shell and inject data into . return new HTMLRewriter() .on("body", { async element(el) { const data = await dataPromise; if (data) { el.prepend( ``, { html: true }, ); } }, }) .transform(shell); }, } satisfies ExportedHandler; async function fetchBootstrapData( env: Env, pathname: string, headers: Headers, ): Promise { try { const res = await fetch(`${env.API_BASE_URL}/api/bootstrap`, { headers: { Cookie: headers.get("Cookie") || "", "X-Request-Path": pathname, }, }); if (!res.ok) return null; return await res.json(); } catch { // If the API is down, the shell still loads and the SPA // falls back to client-side data fetching. return null; } } ``` Explain Code ## Consume prefetched data in your SPA On the client, read `window.__BOOTSTRAP_DATA__` before making any API calls. If the data exists, use it directly. Otherwise, fall back to a normal fetch. src/App.tsx ``` // React example — works the same way in Vue, Svelte, or any other framework. import { useEffect, useState } from "react"; function App() { const [data, setData] = useState(window.__BOOTSTRAP_DATA__ || null); const [loading, setLoading] = useState(!data); useEffect(() => { if (data) return; // Already have prefetched data — skip the API call. fetch("/api/bootstrap") .then((res) => res.json()) .then((result) => { setData(result); setLoading(false); }); }, []); if (loading) return ; return ; } ``` Explain Code Add a type declaration so TypeScript recognizes the global property: global.d.ts ``` declare global { interface Window { __BOOTSTRAP_DATA__?: unknown; } } ``` ## Additional injection techniques You can chain multiple HTMLRewriter handlers to inject more than bootstrap data. ### Set meta tags Inject Open Graph or other `` tags based on the request path. This gives social-media crawlers correct previews without a full server-side rendering framework. TypeScript ``` new HTMLRewriter() .on("head", { element(el) { el.append(``, { html: true, }); }, }) .transform(shell); ``` ### Add CSP nonces Generate a nonce per request and inject it into both the Content-Security-Policy header and each inline ``, { html: true }, ); }, }) .transform(shell); ``` Explain Code ## Related resources * [HTMLRewriter](https://developers.cloudflare.com/workers/runtime-apis/html-rewriter/) — Streaming HTML parser and transformer. * [Workers Static Assets](https://developers.cloudflare.com/workers/static-assets/) — Serve static files alongside your Worker. * [Static Assets routing](https://developers.cloudflare.com/workers/static-assets/routing/) — Configure `run_worker_first` and `not_found_handling`. * [Static Assets binding](https://developers.cloudflare.com/workers/static-assets/binding/) — Reference for the `ASSETS` binding and routing options. * [Custom Domains](https://developers.cloudflare.com/workers/configuration/routing/custom-domains/) — Attach a Worker to a domain as the origin. * [Routes](https://developers.cloudflare.com/workers/configuration/routing/routes/) — Run a Worker in front of an existing origin server. * [Workers Best Practices](https://developers.cloudflare.com/workers/best-practices/workers-best-practices/) — Code patterns and configuration guidance for Workers. ```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/spa-shell/","name":"Single Page App (SPA) shell with bootstrap data"}}]} ``` --- --- title: Stream large JSON description: Parse and transform large JSON request and response bodies using streaming. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) ### Tags [ Middleware ](https://developers.cloudflare.com/search/?tags=Middleware)[ JSON ](https://developers.cloudflare.com/search/?tags=JSON)[ JavaScript ](https://developers.cloudflare.com/search/?tags=JavaScript)[ TypeScript ](https://developers.cloudflare.com/search/?tags=TypeScript) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/examples/streaming-json.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Stream large JSON **Last reviewed:** 4 months ago Parse and transform large JSON request and response bodies using streaming. Use the [Streams API](https://developers.cloudflare.com/workers/runtime-apis/streams/) to process JSON payloads that would exceed a Worker's 128 MB memory limit if fully buffered. Streaming allows you to parse and transform JSON data incrementally as it arrives. This is faster than buffering the entire payload into memory, as your Worker can start processing data incrementally, and allows your Worker to handle multi-gigabyte payloads or files within its memory limits. The [@streamparser/json-whatwg ↗](https://www.npmjs.com/package/@streamparser/json-whatwg) library provides a streaming JSON parser compatible with the Web Streams API. Install the dependency: Terminal window ``` npm install @streamparser/json-whatwg ``` ## Stream a JSON request body This example parses a large JSON request body and extracts specific fields without loading the entire payload into memory. * [ TypeScript ](#tab-panel-7545) * [ JavaScript ](#tab-panel-7546) TypeScript ``` import { JSONParser } from "@streamparser/json-whatwg"; export default { async fetch(request): Promise { const parser = new JSONParser({ paths: ["$.users.*"] }); const users: string[] = []; // Pipe the request body through the JSON parser const reader = request.body .pipeThrough(parser) .getReader(); // Process matching JSON values as they stream in while (true) { const { done, value } = await reader.read(); if (done) break; // Extract only the name field from each user object if (value.value?.name) { users.push(value.value.name); } } return Response.json({ userNames: users }); }, } satisfies ExportedHandler; ``` Explain Code JavaScript ``` import { JSONParser } from "@streamparser/json-whatwg"; export default { async fetch(request) { const parser = new JSONParser({ paths: ["$.users.*"] }); const users = []; // Pipe the request body through the JSON parser const reader = request.body .pipeThrough(parser) .getReader(); // Process matching JSON values as they stream in while (true) { const { done, value } = await reader.read(); if (done) break; // Extract only the name field from each user object if (value.value?.name) { users.push(value.value.name); } } return Response.json({ userNames: users }); }, }; ``` Explain Code ## Stream and transform a JSON response This example fetches a large JSON response from an upstream API, transforms specific fields, and streams the modified response to the client. * [ TypeScript ](#tab-panel-7547) * [ JavaScript ](#tab-panel-7548) TypeScript ``` import { JSONParser } from "@streamparser/json-whatwg"; export default { async fetch(request): Promise { const response = await fetch("https://api.example.com/large-dataset.json"); const parser = new JSONParser({ paths: ["$.items.*"] }); const { readable, writable } = new TransformStream(); const writer = writable.getWriter(); const encoder = new TextEncoder(); // Process the upstream response in the background (async () => { const reader = response.body .pipeThrough(parser) .getReader(); await writer.write(encoder.encode('{"processedItems":[')); let first = true; while (true) { const { done, value } = await reader.read(); if (done) break; // Transform each item as it streams through const item = value.value; const transformed = { id: item.id, title: item.title.toUpperCase(), processed: true, }; if (!first) await writer.write(encoder.encode(",")); first = false; await writer.write(encoder.encode(JSON.stringify(transformed))); } await writer.write(encoder.encode("]}")); await writer.close(); })(); return new Response(readable, { headers: { "Content-Type": "application/json" }, }); }, } satisfies ExportedHandler; ``` Explain Code JavaScript ``` import { JSONParser } from "@streamparser/json-whatwg"; export default { async fetch(request) { const response = await fetch("https://api.example.com/large-dataset.json"); const parser = new JSONParser({ paths: ["$.items.*"] }); const { readable, writable } = new TransformStream(); const writer = writable.getWriter(); const encoder = new TextEncoder(); // Process the upstream response in the background (async () => { const reader = response.body .pipeThrough(parser) .getReader(); await writer.write(encoder.encode('{"processedItems":[')); let first = true; while (true) { const { done, value } = await reader.read(); if (done) break; // Transform each item as it streams through const item = value.value; const transformed = { id: item.id, title: item.title.toUpperCase(), processed: true, }; if (!first) await writer.write(encoder.encode(",")); first = false; await writer.write(encoder.encode(JSON.stringify(transformed))); } await writer.write(encoder.encode("]}")); await writer.close(); })(); return new Response(readable, { headers: { "Content-Type": "application/json" }, }); }, }; ``` Explain Code ## Related resources * [Streams API](https://developers.cloudflare.com/workers/runtime-apis/streams/) \- Learn more about streaming in Workers * [TransformStream](https://developers.cloudflare.com/workers/runtime-apis/streams/transformstream/) \- Create custom stream transformations * [@streamparser/json-whatwg ↗](https://www.npmjs.com/package/@streamparser/json-whatwg) \- Streaming JSON parser documentation ```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/streaming-json/","name":"Stream large JSON"}}]} ``` --- --- title: Turnstile with Workers description: Inject [Turnstile](/turnstile/) implicitly into HTML elements using the HTMLRewriter runtime API. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) ### Tags [ JavaScript ](https://developers.cloudflare.com/search/?tags=JavaScript)[ TypeScript ](https://developers.cloudflare.com/search/?tags=TypeScript)[ Python ](https://developers.cloudflare.com/search/?tags=Python) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/examples/turnstile-html-rewriter.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Turnstile with Workers **Last reviewed:** about 3 years ago Inject [Turnstile](https://developers.cloudflare.com/turnstile/) implicitly into HTML elements using the HTMLRewriter runtime API. * [ JavaScript ](#tab-panel-7549) * [ TypeScript ](#tab-panel-7550) * [ Hono ](#tab-panel-7551) * [ Python ](#tab-panel-7552) JavaScript ``` export default { async fetch(request, env) { const SITE_KEY = env.SITE_KEY; // The Turnstile Sitekey of your widget (pass as env or secret) const TURNSTILE_ATTR_NAME = "your_id_to_replace"; // The id of the element to put a Turnstile widget in let res = await fetch(request); // Instantiate the API to run on specific elements, for example, `head`, `div` let newRes = new HTMLRewriter() // `.on` attaches the element handler and this allows you to match on element/attributes or to use the specific methods per the API .on("head", { element(element) { // In this case, you are using `append` to add a new script to the `head` element element.append( ``, { html: true }, ); }, }) .on("div", { element(element) { // Add a turnstile widget element into if an element with the id of TURNSTILE_ATTR_NAME is found if (element.getAttribute("id") === TURNSTILE_ATTR_NAME) { element.append( `
`, { html: true }, ); } }, }) .transform(res); return newRes; }, }; ``` Explain Code TypeScript ``` export default { async fetch(request, env): Promise { const SITE_KEY = env.SITE_KEY; // The Turnstile Sitekey of your widget (pass as env or secret) const TURNSTILE_ATTR_NAME = "your_id_to_replace"; // The id of the element to put a Turnstile widget in let res = await fetch(request); // Instantiate the API to run on specific elements, for example, `head`, `div` let newRes = new HTMLRewriter() // `.on` attaches the element handler and this allows you to match on element/attributes or to use the specific methods per the API .on("head", { element(element) { // In this case, you are using `append` to add a new script to the `head` element element.append( ``, { html: true }, ); }, }) .on("div", { element(element) { // Add a turnstile widget element into if an element with the id of TURNSTILE_ATTR_NAME is found if (element.getAttribute("id") === TURNSTILE_ATTR_NAME) { element.append( `
`, { html: true }, ); } }, }) .transform(res); return newRes; }, } satisfies ExportedHandler; ``` Explain Code TypeScript ``` import { Hono } from "hono"; interface Env { SITE_KEY: string; SECRET_KEY: string; TURNSTILE_ATTR_NAME?: string; } const app = new Hono<{ Bindings: Env }>(); // Middleware to inject Turnstile widget app.use("*", async (c, next) => { const SITE_KEY = c.env.SITE_KEY; // The Turnstile Sitekey from environment const TURNSTILE_ATTR_NAME = c.env.TURNSTILE_ATTR_NAME || "your_id_to_replace"; // The target element ID // Process the request through the original endpoint await next(); // Only process HTML responses const contentType = c.res.headers.get("content-type"); if (!contentType || !contentType.includes("text/html")) { return; } // Clone the response to make it modifiable const originalResponse = c.res; const responseBody = await originalResponse.text(); // Create an HTMLRewriter instance to modify the HTML const rewriter = new HTMLRewriter() // Add the Turnstile script to the head .on("head", { element(element) { element.append( ``, { html: true }, ); }, }) // Add the Turnstile widget to the target div .on("div", { element(element) { if (element.getAttribute("id") === TURNSTILE_ATTR_NAME) { element.append( `
`, { html: true }, ); } }, }); // Create a new response with the same properties as the original const modifiedResponse = new Response(responseBody, { status: originalResponse.status, statusText: originalResponse.statusText, headers: originalResponse.headers, }); // Transform the response using HTMLRewriter c.res = rewriter.transform(modifiedResponse); }); // Handle POST requests for form submission with Turnstile validation app.post("*", async (c) => { const formData = await c.req.formData(); const token = formData.get("cf-turnstile-response"); const ip = c.req.header("CF-Connecting-IP"); // If no token, return an error if (!token) { return c.text("Missing Turnstile token", 400); } // Prepare verification data const verifyFormData = new FormData(); verifyFormData.append("secret", c.env.SECRET_KEY || ""); verifyFormData.append("response", token.toString()); if (ip) verifyFormData.append("remoteip", ip); // Verify the token with Turnstile API const verifyResult = await fetch( "https://challenges.cloudflare.com/turnstile/v0/siteverify", { method: "POST", body: verifyFormData, }, ); const outcome = await verifyResult.json<{ success: boolean }>; // If verification fails, return an error if (!outcome.success) { return c.text("The provided Turnstile token was not valid!", 401); } // If verification succeeds, proceed with the original request // You would typically handle the form submission logic here // For this example, we'll just send a success response return c.text("Form submission successful!"); }); // Default handler for GET requests app.get("*", async (c) => { // Fetch the original content (you'd replace this with your actual content source) return await fetch(c.req.raw); }); export default app; ``` Explain Code Python ``` from workers import WorkerEntrypoint from pyodide.ffi import create_proxy from js import HTMLRewriter, fetch class Default(WorkerEntrypoint): async def fetch(self, request): site_key = self.env.SITE_KEY attr_name = self.env.TURNSTILE_ATTR_NAME res = await fetch(request) class Append: def element(self, element): s = '' element.append(s, {"html": True}) class AppendOnID: def __init__(self, name): self.name = name def element(self, element): # You are using the `getAttribute` method here to retrieve the `id` or `class` of an element if element.getAttribute("id") == self.name: div = f'
' element.append(div, { "html": True }) # Instantiate the API to run on specific elements, for example, `head`, `div` head = create_proxy(Append()) div = create_proxy(AppendOnID(attr_name)) new_res = HTMLRewriter.new().on("head", head).on("div", div).transform(res) return new_res ``` Explain Code Note This is only half the implementation for Turnstile. The corresponding token that is a result of a widget being rendered also needs to be verified using the [Siteverify API](https://developers.cloudflare.com/turnstile/get-started/server-side-validation/). Refer to the example below for one such implementation. JavaScript ``` async function handlePost(request, env) { const body = await request.formData(); // Turnstile injects a token in `cf-turnstile-response`. const token = body.get('cf-turnstile-response'); const ip = request.headers.get('CF-Connecting-IP'); // Validate the token by calling the `/siteverify` API. let formData = new FormData(); // `secret_key` here is the Turnstile Secret key, which should be set using Wrangler secrets formData.append('secret', self.env.SECRET_KEY); formData.append('response', token); formData.append('remoteip', ip); //This is optional. const url = 'https://challenges.cloudflare.com/turnstile/v0/siteverify'; const result = await fetch(url, { body: formData, method: 'POST', }); const outcome = await result.json(); if (!outcome.success) { return new Response('The provided Turnstile token was not valid!', { status: 401 }); } // The Turnstile token was successfully validated. Proceed with your application logic. // Validate login, redirect user, etc. // Clone the original request with a new body const newRequest = new Request(request, { body: request.body, // Reuse the body method: request.method, headers: request.headers }); return await fetch(newRequest); } export default { async fetch(request, env) { const SITE_KEY = env.SITE_KEY; // The Turnstile Sitekey of your widget (pass as env or secret) const TURNSTILE_ATTR_NAME = 'your_id_to_replace'; // The id of the element to put a Turnstile widget in let res = await fetch(request) if (request.method === 'POST') { return handlePost(request, env) } // Instantiate the API to run on specific elements, for example, `head`, `div` let newRes = new HTMLRewriter() // `.on` attaches the element handler and this allows you to match on element/attributes or to use the specific methods per the API .on('head', { element(element) { // In this case, you are using `append` to add a new script to the `head` element element.append(``, { html: true }); }, }) .on('div', { element(element) { // You are using the `getAttribute` method here to retrieve the `id` or `class` of an element if (element.getAttribute('id') === ) { element.append(`
`, { html: true }); } }, }) .transform(res); return newRes } } ``` Explain Code Prevent potential errors when accessing request.body The body of a [Request ↗](https://developer.mozilla.org/en-US/docs/Web/API/Request) can only be accessed once. If you previously used `request.formData()` in the same request, you may encounter a TypeError when attempting to access `request.body`. To avoid errors, create a clone of the Request object with `request.clone()` for each subsequent attempt to access a Request's body. Keep in mind that Workers have a [memory limit of 128 MB per Worker](https://developers.cloudflare.com/workers/platform/limits/#memory) and loading particularly large files into a Worker's memory multiple times may reach this limit. To ensure memory usage does not reach this limit, consider using [Streams](https://developers.cloudflare.com/workers/runtime-apis/streams/). ```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/turnstile-html-rewriter/","name":"Turnstile with Workers"}}]} ``` --- --- title: Using the WebSockets API description: Use the WebSockets API to communicate in real time with your Cloudflare Workers. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) ### Tags [ WebSockets ](https://developers.cloudflare.com/search/?tags=WebSockets)[ JavaScript ](https://developers.cloudflare.com/search/?tags=JavaScript)[ Rust ](https://developers.cloudflare.com/search/?tags=Rust) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/examples/websockets.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Using the WebSockets API **Last reviewed:** about 5 years ago Use the WebSockets API to communicate in real time with your Cloudflare Workers. WebSockets allow you to communicate in real time with your Cloudflare Workers serverless functions. In this guide, you will learn the basics of WebSockets on Cloudflare Workers, both from the perspective of writing WebSocket servers in your Workers functions, as well as connecting to and working with those WebSocket servers as a client. WebSockets are open connections sustained between the client and the origin server. Inside a WebSocket connection, the client and the origin can pass data back and forth without having to reestablish sessions. This makes exchanging data within a WebSocket connection fast. WebSockets are often used for real-time applications such as live chat and gaming. Note WebSockets utilize an event-based system for receiving and sending messages, much like the Workers runtime model of responding to events. Note If your application needs to coordinate among multiple WebSocket connections, such as a chat room or game match, you will need clients to send messages to a single-point-of-coordination. Durable Objects provide a single-point-of-coordination for Cloudflare Workers, and are often used in parallel with WebSockets to persist state over multiple clients and connections. In this case, refer to [Durable Objects](https://developers.cloudflare.com/durable-objects/) to get started, and prefer using the Durable Objects' extended [WebSockets API](https://developers.cloudflare.com/durable-objects/best-practices/websockets/). ## Write a WebSocket Server WebSocket servers in Cloudflare Workers allow you to receive messages from a client in real time. This guide will show you how to set up a WebSocket server in Workers. A client can make a WebSocket request in the browser by instantiating a new instance of `WebSocket`, passing in the URL for your Workers function: JavaScript ``` // In client-side JavaScript, connect to your Workers function using WebSockets: const websocket = new WebSocket( "wss://example-websocket.signalnerve.workers.dev", ); ``` Note For more details about creating and working with WebSockets in the client, refer to [Writing a WebSocket client](#write-a-websocket-client). When an incoming WebSocket request reaches the Workers function, it will contain an `Upgrade` header, set to the string value `websocket`. Check for this header before continuing to instantiate a WebSocket: * [ JavaScript ](#tab-panel-7553) * [ Rust ](#tab-panel-7554) JavaScript ``` async function handleRequest(request) { const upgradeHeader = request.headers.get('Upgrade'); if (!upgradeHeader || upgradeHeader !== 'websocket') { return new Response('Expected Upgrade: websocket', { status: 426 }); } } ``` ``` use worker::*; #[event(fetch)] async fn fetch(req: HttpRequest, _env: Env, _ctx: Context) -> Result { let upgrade_header = match req.headers().get("Upgrade") { Some(h) => h.to_str().unwrap(), None => "", }; if upgrade_header != "websocket" { return worker::Response::error("Expected Upgrade: websocket", 426); } } ``` Explain Code After you have appropriately checked for the `Upgrade` header, you can create a new instance of `WebSocketPair`, which contains server and client WebSockets. One of these WebSockets should be handled by the Workers function and the other should be returned as part of a `Response` with the [101 status code ↗](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/101), indicating the request is switching protocols: * [ JavaScript ](#tab-panel-7555) * [ Rust ](#tab-panel-7556) JavaScript ``` async function handleRequest(request) { const upgradeHeader = request.headers.get('Upgrade'); if (!upgradeHeader || upgradeHeader !== 'websocket') { return new Response('Expected Upgrade: websocket', { status: 426 }); } const webSocketPair = new WebSocketPair(); const client = webSocketPair[0], server = webSocketPair[1]; return new Response(null, { status: 101, webSocket: client, }); } ``` Explain Code ``` use worker::*; #[event(fetch)] async fn fetch(req: HttpRequest, _env: Env, _ctx: Context) -> Result { let upgrade_header = match req.headers().get("Upgrade") { Some(h) => h.to_str().unwrap(), None => "", }; if upgrade_header != "websocket" { return worker::Response::error("Expected Upgrade: websocket", 426); } let ws = WebSocketPair::new()?; let client = ws.client; let server = ws.server; server.accept()?; worker::Response::from_websocket(client) } ``` Explain Code The `WebSocketPair` constructor returns an Object, with the `0` and `1` keys each holding a `WebSocket` instance as its value. It is common to grab the two WebSockets from this pair using [Object.values ↗](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global%5Fobjects/Object/values) and [ES6 destructuring ↗](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring%5Fassignment), as seen in the below example. In order to begin communicating with the `client` WebSocket in your Worker, call `accept` on the `server` WebSocket. This will tell the Workers runtime that it should listen for WebSocket data and keep the connection open with your `client` WebSocket: * [ JavaScript ](#tab-panel-7557) * [ Rust ](#tab-panel-7558) JavaScript ``` async function handleRequest(request) { const upgradeHeader = request.headers.get('Upgrade'); if (!upgradeHeader || upgradeHeader !== 'websocket') { return new Response('Expected Upgrade: websocket', { status: 426 }); } const webSocketPair = new WebSocketPair(); const [client, server] = Object.values(webSocketPair); server.accept(); return new Response(null, { status: 101, webSocket: client, }); } ``` Explain Code ``` use worker::*; #[event(fetch)] async fn fetch(req: HttpRequest, _env: Env, _ctx: Context) -> Result { let upgrade_header = match req.headers().get("Upgrade") { Some(h) => h.to_str().unwrap(), None => "", }; if upgrade_header != "websocket" { return worker::Response::error("Expected Upgrade: websocket", 426); } let ws = WebSocketPair::new()?; let client = ws.client; let server = ws.server; server.accept()?; worker::Response::from_websocket(client) } ``` Explain Code WebSockets emit a number of [Events](https://developers.cloudflare.com/workers/runtime-apis/websockets/#events) that can be connected to using `addEventListener`. The below example hooks into the `message` event and emits a `console.log` with the data from it: * [ JavaScript ](#tab-panel-7559) * [ Rust ](#tab-panel-7560) * [ Hono ](#tab-panel-7561) JavaScript ``` async function handleRequest(request) { const upgradeHeader = request.headers.get('Upgrade'); if (!upgradeHeader || upgradeHeader !== 'websocket') { return new Response('Expected Upgrade: websocket', { status: 426 }); } const webSocketPair = new WebSocketPair(); const [client, server] = Object.values(webSocketPair); server.accept(); server.addEventListener('message', event => { console.log(event.data); }); return new Response(null, { status: 101, webSocket: client, }); } ``` Explain Code ``` use futures::StreamExt; use worker::*; #[event(fetch)] async fn fetch(req: HttpRequest, _env: Env, _ctx: Context) -> Result { let upgrade_header = match req.headers().get("Upgrade") { Some(h) => h.to_str().unwrap(), None => "", }; if upgrade_header != "websocket" { return worker::Response::error("Expected Upgrade: websocket", 426); } let ws = WebSocketPair::new()?; let client = ws.client; let server = ws.server; server.accept()?; wasm_bindgen_futures::spawn_local(async move { let mut event_stream = server.events().expect("could not open stream"); while let Some(event) = event_stream.next().await { match event.expect("received error in websocket") { WebsocketEvent::Message(msg) => server.send(&msg.text()).unwrap(), WebsocketEvent::Close(event) => console_log!("{:?}", event), } } }); worker::Response::from_websocket(client) } ``` Explain Code TypeScript ``` import { Hono } from 'hono' import { upgradeWebSocket } from 'hono/cloudflare-workers' const app = new Hono() app.get( '*', upgradeWebSocket((c) => { return { onMessage(event, ws) { console.log('Received message from client:', event.data) ws.send(`Echo: ${event.data}`) }, onClose: () => { console.log('WebSocket closed:', event) }, onError: () => { console.error('WebSocket error:', event) }, } }) ) export default app; ``` Explain Code ### Connect to the WebSocket server from a client Writing WebSocket clients that communicate with your Workers function is a two-step process: first, create the WebSocket instance, and then attach event listeners to it: JavaScript ``` const websocket = new WebSocket( "wss://websocket-example.signalnerve.workers.dev", ); websocket.addEventListener("message", (event) => { console.log("Message received from server"); console.log(event.data); }); ``` WebSocket clients can send messages back to the server using the [send](https://developers.cloudflare.com/workers/runtime-apis/websockets/#send) function: JavaScript ``` websocket.send("MESSAGE"); ``` When the WebSocket interaction is complete, the client can close the connection using [close](https://developers.cloudflare.com/workers/runtime-apis/websockets/#close): JavaScript ``` websocket.close(); ``` For an example of this in practice, refer to the [websocket-template ↗](https://github.com/cloudflare/websocket-template) to get started with WebSockets. ## Write a WebSocket client Cloudflare Workers supports the `new WebSocket(url)` constructor. A Worker can establish a WebSocket connection to a remote server in the same manner as the client implementation described above. Additionally, Cloudflare supports establishing WebSocket connections by making a fetch request to a URL with the `Upgrade` header set. JavaScript ``` async function websocket(url) { // Make a fetch request including `Upgrade: websocket` header. // The Workers Runtime will automatically handle other requirements // of the WebSocket protocol, like the Sec-WebSocket-Key header. let resp = await fetch(url, { headers: { Upgrade: "websocket", }, }); // If the WebSocket handshake completed successfully, then the // response has a `webSocket` property. let ws = resp.webSocket; if (!ws) { throw new Error("server didn't accept WebSocket"); } // Call accept() to indicate that you'll be handling the socket here // in JavaScript, as opposed to returning it on to a client. // You can pass { allowHalfOpen: true } if you need to coordinate // the close handshake manually (for example, when proxying). ws.accept(); // Now you can send and receive messages like before. ws.send("hello"); ws.addEventListener("message", (msg) => { console.log(msg.data); }); } ``` Explain Code ## WebSocket close behavior With the [web\_socket\_auto\_reply\_to\_close](https://developers.cloudflare.com/workers/configuration/compatibility-flags/#websocket-auto-reply-to-close) compatibility flag (enabled by default on compatibility dates on or after `2026-04-07`), the Workers runtime automatically replies to incoming Close frames and transitions `readyState` to `CLOSED` before firing the `close` event. You do not need to call `close()` in your `close` event handler, but doing so is safe (the call is silently ignored). If you need half-open behavior (for example, for WebSocket proxying), pass `{ allowHalfOpen: true }` to `accept()`. Note that `new WebSocket(url)` always auto-replies after this flag takes effect. To get half-open behavior for a client WebSocket, use the `fetch()`\-based pattern shown above and call `ws.accept({ allowHalfOpen: true })`. For more details, refer to [WebSocket close behavior](https://developers.cloudflare.com/workers/runtime-apis/websockets/#close-behavior). ## WebSocket compression Cloudflare Workers supports WebSocket compression. Refer to [WebSocket Compression](https://developers.cloudflare.com/workers/configuration/compatibility-flags/#websocket-compression) for more information. ```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/websockets/","name":"Using the WebSockets API"}}]} ``` --- --- title: Tutorials description: View tutorials to help you get started with Workers. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/tutorials/index.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Tutorials View tutorials to help you get started with Workers. ## Docs | Name | Last Updated | Difficulty | | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ | ------------ | | [Deploy a Browser Rendering Worker with Durable Objects](https://developers.cloudflare.com/browser-rendering/how-to/browser-rendering-with-do/) | over 2 years ago | Beginner | | [Generate OG images for Astro sites](https://developers.cloudflare.com/browser-rendering/how-to/og-images-astro/) | Intermediate | | | [Build a Comments API](https://developers.cloudflare.com/d1/tutorials/build-a-comments-api/) | 29 days ago | Intermediate | | [Deploy an Express.js application on Cloudflare Workers](https://developers.cloudflare.com/workers/tutorials/deploy-an-express-app/) | 6 months ago | Beginner | | [Connect to a PostgreSQL database with Cloudflare Workers](https://developers.cloudflare.com/workers/tutorials/postgres/) | 10 months ago | Beginner | | [Query D1 using Prisma ORM](https://developers.cloudflare.com/d1/tutorials/d1-and-prisma-orm/) | 11 months ago | Beginner | | [Migrate from Netlify to Workers](https://developers.cloudflare.com/workers/static-assets/migration-guides/netlify-to-workers/) | 11 months ago | Beginner | | [Migrate from Vercel to Workers](https://developers.cloudflare.com/workers/static-assets/migration-guides/vercel-to-workers/) | 12 months ago | Beginner | | [Tutorial - React SPA with an API](https://developers.cloudflare.com/workers/vite-plugin/tutorial/) | about 1 year ago | | | [Connect to a MySQL database with Cloudflare Workers](https://developers.cloudflare.com/workers/tutorials/mysql/) | about 1 year ago | Beginner | | [Set up and use a Prisma Postgres database](https://developers.cloudflare.com/workers/tutorials/using-prisma-postgres-with-workers/) | about 1 year ago | Beginner | | [Store and Catalog AI Generated Images with R2 (Part 3)](https://developers.cloudflare.com/workers-ai/guides/tutorials/image-generation-playground/image-generator-store-and-catalog/) | about 1 year ago | Beginner | | [Build a Retrieval Augmented Generation (RAG) AI](https://developers.cloudflare.com/workers-ai/guides/tutorials/build-a-retrieval-augmented-generation-ai/) | over 1 year ago | Beginner | | [Using BigQuery with Workers AI](https://developers.cloudflare.com/workers-ai/guides/tutorials/using-bigquery-with-workers-ai/) | over 1 year ago | Beginner | | [How to Build an Image Generator using Workers AI](https://developers.cloudflare.com/workers-ai/guides/tutorials/image-generation-playground/) | over 1 year ago | Beginner | | [Build an AI Image Generator Playground (Part 1)](https://developers.cloudflare.com/workers-ai/guides/tutorials/image-generation-playground/image-generator-flux/) | over 1 year ago | Beginner | | [Add New AI Models to your Playground (Part 2)](https://developers.cloudflare.com/workers-ai/guides/tutorials/image-generation-playground/image-generator-flux-newmodels/) | over 1 year ago | Beginner | | [Use event notification to summarize PDF files on upload](https://developers.cloudflare.com/r2/tutorials/summarize-pdf/) | over 1 year ago | Intermediate | | [Handle rate limits of external APIs](https://developers.cloudflare.com/queues/tutorials/handle-rate-limits/) | over 1 year ago | Beginner | | [Build an API to access D1 using a proxy Worker](https://developers.cloudflare.com/d1/tutorials/build-an-api-to-access-d1/) | over 1 year ago | Intermediate | | [Deploy a Worker](https://developers.cloudflare.com/pulumi/tutorial/hello-world/) | over 1 year ago | Beginner | | [Build a web crawler with Queues and Browser Rendering](https://developers.cloudflare.com/queues/tutorials/web-crawler-with-browser-rendering/) | over 1 year ago | Intermediate | | [Create a fine-tuned OpenAI model with R2](https://developers.cloudflare.com/workers/tutorials/create-finetuned-chatgpt-ai-models-with-r2/) | almost 2 years ago | Intermediate | | [Build a Slackbot](https://developers.cloudflare.com/workers/tutorials/build-a-slackbot/) | almost 2 years ago | Beginner | | [Use Workers KV directly from Rust](https://developers.cloudflare.com/workers/tutorials/workers-kv-from-rust/) | almost 2 years ago | Intermediate | | [Build a todo list Jamstack application](https://developers.cloudflare.com/workers/tutorials/build-a-jamstack-app/) | almost 2 years ago | Beginner | | [Send Emails With Postmark](https://developers.cloudflare.com/workers/tutorials/send-emails-with-postmark/) | almost 2 years ago | Beginner | | [Send Emails With Resend](https://developers.cloudflare.com/workers/tutorials/send-emails-with-resend/) | almost 2 years ago | Beginner | | [Log and store upload events in R2 with event notifications](https://developers.cloudflare.com/r2/tutorials/upload-logs-event-notifications/) | about 2 years ago | Beginner | | [Create custom headers for Cloudflare Access-protected origins with Workers](https://developers.cloudflare.com/cloudflare-one/tutorials/access-workers/) | over 2 years ago | Intermediate | | [Create a serverless, globally distributed time-series API with Timescale](https://developers.cloudflare.com/hyperdrive/tutorials/serverless-timeseries-api-with-timescale/) | over 2 years ago | Beginner | | [GitHub SMS notifications using Twilio](https://developers.cloudflare.com/workers/tutorials/github-sms-notifications-using-twilio/) | over 2 years ago | Beginner | | [Deploy a real-time chat application](https://developers.cloudflare.com/workers/tutorials/deploy-a-realtime-chat-app/) | over 2 years ago | Intermediate | | [Build a QR code generator](https://developers.cloudflare.com/workers/tutorials/build-a-qr-code-generator/) | almost 3 years ago | Beginner | | [Securely access and upload assets with Cloudflare R2](https://developers.cloudflare.com/workers/tutorials/upload-assets-with-r2/) | almost 3 years ago | Beginner | | [OpenAI GPT function calling with JavaScript and Cloudflare Workers](https://developers.cloudflare.com/workers/tutorials/openai-function-calls-workers/) | almost 3 years ago | Beginner | | [Handle form submissions with Airtable](https://developers.cloudflare.com/workers/tutorials/handle-form-submissions-with-airtable/) | almost 3 years ago | Beginner | | [Connect to and query your Turso database using Workers](https://developers.cloudflare.com/workers/tutorials/connect-to-turso-using-workers/) | about 3 years ago | Beginner | | [Generate YouTube thumbnails with Workers and Cloudflare Image Resizing](https://developers.cloudflare.com/workers/tutorials/generate-youtube-thumbnails-with-workers-and-images/) | about 3 years ago | Intermediate | ## Videos [ Play ](https://youtube.com/watch?v=xu4Wb-IppmM) OpenAI Relay Server on Cloudflare Workers In this video, Craig Dennis walks you through the deployment of OpenAI's relay server to use with their realtime API. [ Play ](https://youtube.com/watch?v=B2bLUc3iOsI) Deploy your React App to Cloudflare Workers Learn how to deploy an existing React application to Cloudflare Workers. [ Play ](https://youtube.com/watch?v=L6gR4Yr3UW8) Cloudflare Workflows | Schedule and Sleep For Your Apps (Part 3 of 3) Cloudflare Workflows allows you to initiate sleep as an explicit step, which can be useful when you want a Workflow to wait, schedule work ahead, or pause until an input or other external state is ready. [ Play ](https://youtube.com/watch?v=y4PPsvHrQGA) Cloudflare Workflows | Batching and Monitoring Your Durable Execution (Part 2 of 3) Workflows exposes metrics such as execution, error rates, steps, and total duration! [ Play ](https://youtube.com/watch?v=slS4RBV0SBk) Cloudflare Workflows | Introduction (Part 1 of 3) In this video, we introduce Cloudflare Workflows, the Newest Developer Platform Primitive at Cloudflare. [ Play ](https://youtube.com/watch?v=W45MIi%5Ft%5Fgo) Building Front-End Applications | Now Supported by Cloudflare Workers You can now build front-end applications, just like you do on Cloudflare Pages, but with the added benefit of Workers. [ Play ](https://youtube.com/watch?v=10-kiyJNr8s) Build a private AI chatbot using Meta's Llama 3.1 In this video, you will learn how to set up a private AI chat powered by Llama 3.1 for secure, fast interactions, deploy the model on Cloudflare Workers for serverless, scalable performance and use Cloudflare's Workers AI for seamless integration and edge computing benefits. [ Play ](https://youtube.com/watch?v=HXOpxNaKUzw) How to Build Event-Driven Applications with Cloudflare Queues In this video, we demonstrate how to build an event-driven application using Cloudflare Queues. Event-driven system lets you decouple services, allowing them to process and scale independently. [ Play ](https://youtube.com/watch?v=bwJkwD-F0kQ) Welcome to the Cloudflare Developer Channel Welcome to the Cloudflare Developers YouTube channel. We've got tutorials and working demos and everything you need to level up your projects. Whether you're working on your next big thing or just dorking around with some side projects, we've got you covered! So why don't you come hang out, subscribe to our developer channel and together we'll build something awesome. You're gonna love it. [ Play ](https://youtube.com/watch?v=doKt9wWQF9A) AI meets Maps | Using Cloudflare AI, Langchain, Mapbox, Folium and Streamlit Welcome to RouteMe, a smart tool that helps you plan the most efficient route between landmarks in any city. Powered by Cloudflare Workers AI, Langchain and Mapbox. This Streamlit webapp uses LLMs and Mapbox off my scripts API to solve the classic traveling salesman problem, turning your sightseeing into an optimized adventure! [ Play ](https://youtube.com/watch?v=9IjfyBJsJRQ) Use Vectorize to add additional context to your AI Applications through RAG A RAG based AI Chat app that uses Vectorize to access video game data for employees of Gamertown. [ Play ](https://youtube.com/watch?v=dttu4QtKkO0) Build Rust Powered Apps In this video, we will show you how to build a global database using workers-rs to keep track of every country and city you’ve visited. [ Play ](https://youtube.com/watch?v=QTsaAhFvX9o) Stateful Apps with Cloudflare Workers Learn how to access external APIs, cache and retrieve data using Workers KV, and create SQL-driven applications with Cloudflare D1. [ Play ](https://youtube.com/watch?v=H7Qe96fqg1M) Learn Cloudflare Workers - Full Course for Beginners Learn how to build your first Cloudflare Workers application and deploy it to Cloudflare's global network. [ Play ](https://youtube.com/watch?v=CHfKeFakGAI) How to use Cloudflare AI models and inference in Python with Jupyter Notebooks Cloudflare Workers AI provides a ton of AI models and inference capabilities. In this video, we will explore how to make use of Cloudflare’s AI model catalog using a Python Jupyter Notebook. [ Play ](https://youtube.com/watch?v=9JM5Z0KzQsQ) Learn AI Development (models, embeddings, vectors) In this workshop, Kristian Freeman, Cloudflare Developer Advocate, teaches the basics of AI Development - models, embeddings, and vectors (including vector databases). [ Play ](https://youtube.com/watch?v=idKdjA8t0jw) Optimize your AI App & fine-tune models (AI Gateway, R2) In this workshop, Kristian Freeman, Cloudflare Developer Advocate, shows how to optimize your existing AI applications with Cloudflare AI Gateway, and how to finetune OpenAI models using R2. ```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/tutorials/","name":"Tutorials"}}]} ``` --- --- title: Build a todo list Jamstack application description: This tutorial explains how to build a todo list application using HTML, CSS, and JavaScript. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) ### Tags [ JavaScript ](https://developers.cloudflare.com/search/?tags=JavaScript) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/tutorials/build-a-jamstack-app.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Build a todo list Jamstack application **Last reviewed:** almost 2 years ago In this tutorial, you will build a todo list application using HTML, CSS, and JavaScript. The application data will be stored in [Workers KV](https://developers.cloudflare.com/kv/api/). ![Preview of a finished todo list. Continue reading for instructions on how to set up a todo list.](https://developers.cloudflare.com/_astro/finished.CHDh55j7_Z2saS5S.webp) Before starting this project, you should have some experience with HTML, CSS, and JavaScript. You will learn: 1. How building with Workers makes allows you to focus on writing code and ship finished products. 2. How the addition of Workers KV makes this tutorial a great introduction to building full, data-driven applications. If you would like to see the finished code for this project, find the [project on GitHub ↗](https://github.com/lauragift21/cloudflare-workers-todos) and refer to the [live demo ↗](https://todos.examples.workers.dev/) to review what you will be building. ## Before you start All of the tutorials assume you have already completed the [Get started guide](https://developers.cloudflare.com/workers/get-started/guide/), which gets you set up with a Cloudflare Workers account, [C3 ↗](https://github.com/cloudflare/workers-sdk/tree/main/packages/create-cloudflare), and [Wrangler](https://developers.cloudflare.com/workers/wrangler/install-and-update/). ## 1\. Create a new Workers project First, use the [create-cloudflare ↗](https://www.npmjs.com/package/create-cloudflare) CLI tool to create a new Cloudflare Workers project named `todos`. In this tutorial, you will use the default `Hello World` template to create a Workers project. npm yarn pnpm ``` npm create cloudflare@latest -- todos ``` ``` yarn create cloudflare todos ``` ``` pnpm create cloudflare@latest todos ``` For setup, select the following options: * For _What would you like to start with?_, choose `Hello World example`. * For _Which template would you like to use?_, choose `Worker only`. * For _Which language do you want to use?_, choose `JavaScript`. * For _Do you want to use git for version control?_, choose `Yes`. * For _Do you want to deploy your application?_, choose `No` (we will be making some changes before deploying). Move into your newly created directory: Terminal window ``` cd todos ``` Inside of your new `todos` Worker project directory, `index.js` represents the entry point to your Cloudflare Workers application. All incoming HTTP requests to a Worker are passed to the [fetch() handler](https://developers.cloudflare.com/workers/runtime-apis/handlers/fetch/) as a [request](https://developers.cloudflare.com/workers/runtime-apis/request/) object. After a request is received by the Worker, the response your application constructs will be returned to the user. This tutorial will guide you through understanding how the request/response pattern works and how you can use it to build fully featured applications. JavaScript ``` export default { async fetch(request, env, ctx) { return new Response("Hello World!"); }, }; ``` In your default `index.js` file, you can see that request/response pattern in action. The `fetch` constructs a new `Response` with the body text `'Hello World!'`. When a Worker receives a `request`, the Worker returns the newly constructed response to the client. Your Worker will serve new responses directly from [Cloudflare's global network ↗](https://www.cloudflare.com/network) instead of continuing to your origin server. A standard server would accept requests and return responses. Cloudflare Workers allows you to respond by constructing responses directly on the Cloudflare global network. ## 2\. Review project details Any project you deploy to Cloudflare Workers can make use of modern JavaScript tooling like [ES modules](https://developers.cloudflare.com/workers/reference/migrate-to-module-workers/), `npm` packages, and [async/await ↗](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async%5Ffunction) functions to build your application. In addition to writing Workers, you can use Workers to [build full applications](https://developers.cloudflare.com/workers/tutorials/build-a-slackbot/) using the same tooling and process as in this tutorial. In this tutorial, you will build a todo list application running on Workers that allows reading data from a [KV](https://developers.cloudflare.com/kv/) store and using the data to populate an HTML response to send to the client. The work needed to create this application is split into three tasks: 1. Write data to KV. 2. Rendering data from KV. 3. Adding todos from the application UI. For the remainder of this tutorial you will complete each task, iterating on your application, and then publish it to your own domain. ## 3\. Write data to KV To begin, you need to understand how to populate your todo list with actual data. To do this, use [Cloudflare Workers KV](https://developers.cloudflare.com/kv/) — a key-value store that you can access inside of your Worker to read and write data. To get started with KV, set up a namespace. All of your cached data will be stored inside that namespace and, with configuration, you can access that namespace inside the Worker with a predefined variable. Use Wrangler to create a new namespace called `TODOS` with the [kv namespace create command](https://developers.cloudflare.com/workers/wrangler/commands/kv/#kv-namespace-create) and get the associated namespace ID by running the following command in your terminal: Create a new KV namespace ``` npx wrangler kv namespace create "TODOS" --preview ``` The associated namespace can be combined with a `--preview` flag to interact with a preview namespace instead of a production namespace. Namespaces can be added to your application by defining them inside your Wrangler configuration. Copy your newly created namespace ID, and in your [Wrangler configuration file](https://developers.cloudflare.com/workers/wrangler/configuration/), define a `kv_namespaces` key to set up your namespace: * [ wrangler.jsonc ](#tab-panel-7926) * [ wrangler.toml ](#tab-panel-7927) JSONC ``` { "kv_namespaces": [ { "binding": "TODOS", "id": "", "preview_id": "" } ] } ``` TOML ``` [[kv_namespaces]] binding = "TODOS" id = "" preview_id = "" ``` The defined namespace, `TODOS`, will now be available inside of your codebase. With that, it is time to understand the [KV API](https://developers.cloudflare.com/kv/api/). A KV namespace has three primary methods you can use to interface with your cache: `get`, `put`, and `delete`. Start storing data by defining an initial set of data, which you will put inside of the cache using the `put` method. The following example defines a `defaultData` object instead of an array of todo items. You may want to store metadata and other information inside of this cache object later on. Given that data object, use `JSON.stringify` to add a string into the cache: JavaScript ``` export default { async fetch(request, env, ctx) { const defaultData = { todos: [ { id: 1, name: "Finish the Cloudflare Workers blog post", completed: false, }, ], }; await env.TODOS.put("data", JSON.stringify(defaultData)); return new Response("Hello World!"); }, }; ``` Explain Code Workers KV is an eventually consistent, global datastore. Any writes within a region are immediately reflected within that same region but will not be immediately available in other regions. However, those writes will eventually be available everywhere and, at that point, Workers KV guarantees that data within each region will be consistent. Given the presence of data in the cache and the assumption that your cache is eventually consistent, this code needs a slight adjustment: the application should check the cache and use its value, if the key exists. If it does not, you will use `defaultData` as the data source for now (it should be set in the future) and write it to the cache for future use. After breaking out the code into a few functions for simplicity, the result looks like this: JavaScript ``` export default { async fetch(request, env, ctx) { const defaultData = { todos: [ { id: 1, name: "Finish the Cloudflare Workers blog post", completed: false, }, ], }; const setCache = (data) => env.TODOS.put("data", data); const getCache = () => env.TODOS.get("data"); let data; const cache = await getCache(); if (!cache) { await setCache(JSON.stringify(defaultData)); data = defaultData; } else { data = JSON.parse(cache); } return new Response(JSON.stringify(data)); }, }; ``` Explain Code ## Render data from KV Given the presence of data in your code, which is the cached data object for your application, you should take this data and render it in a user interface. To do this, make a new `html` variable in your Workers script and use it to build up a static HTML template that you can serve to the client. In `fetch`, construct a new `Response` with a `Content-Type: text/html` header and serve it to the client: JavaScript ``` const html = ` Todos

Todos

`; async fetch (request, env, ctx) { // previous code return new Response(html, { headers: { 'Content-Type': 'text/html' } }); } ``` Explain Code You have a static HTML site being rendered and you can begin populating it with data. In the body, add a `div` tag with an `id` of `todos`: JavaScript ``` const html = ` Todos

Todos

`; ``` Explain Code Add a ` `; ``` Explain Code Your static page can take in `window.todos` and render HTML based on it, but you have not actually passed in any data from KV. To do this, you will need to make a few changes. First, your `html` variable will change to a function. The function will take in a `todos` argument, which will populate the `window.todos` variable in the above code sample: JavaScript ``` const html = (todos) => ` `; ``` Explain Code This code updates the cache. Remember that the KV cache is eventually consistent — even if you were to update your Worker to read from the cache and return it, you have no guarantees it will actually be up to date. Instead, update the list of todos locally, by taking your original code for rendering the todo list, making it a reusable function called `populateTodos`, and calling it when the page loads and when the cache request has finished: JavaScript ``` const html = (todos) => ` `; ``` Explain Code With the client-side code in place, deploying the new version of the function should put all these pieces together. The result is an actual dynamic todo list. ## 5\. Update todos from the application UI For the final piece of your todo list, you need to be able to update todos — specifically, marking them as completed. Luckily, a great deal of the infrastructure for this work is already in place. You can update the todo list data in the cache, as evidenced by your `createTodo` function. Performing updates on a todo is more of a client-side task than a Worker-side one. To start, the `populateTodos` function can be updated to generate a `div` for each todo. In addition, move the name of the todo into a child element of that `div`: JavaScript ``` const html = (todos) => ` `; ``` Explain Code You have designed the client-side part of this code to handle an array of todos and render a list of HTML elements. There is a number of things that you have been doing that you have not quite had a use for yet – specifically, the inclusion of IDs and updating the todo's completed state. These things work well together to actually support updating todos in the application UI. To start, it would be useful to attach the ID of each todo in the HTML. By doing this, you can then refer to the element later in order to correspond it to the todo in the JavaScript part of your code. Data attributes and the corresponding `dataset` method in JavaScript are a perfect way to implement this. When you generate your `div` element for each todo, you can attach a data attribute called todo to each `div`: JavaScript ``` const html = (todos) => ` `; ``` Explain Code Inside your HTML, each `div` for a todo now has an attached data attribute, which looks like: ```
``` You can now generate a checkbox for each todo element. This checkbox will default to unchecked for new todos but you can mark it as checked as the element is rendered in the window: JavaScript ``` const html = (todos) => ` `; ``` Explain Code The checkbox is set up to correctly reflect the value of completed on each todo but it does not yet update when you actually check the box. To do this, attach the `completeTodo` function as an event listener on the `click` event. Inside the function, inspect the checkbox element, find its parent (the todo `div`), and use its `todo` data attribute to find the corresponding todo in the data array. You can toggle the completed status, update its properties, and rerender the UI: JavaScript ``` const html = (todos) => ` `; ``` Explain Code The final result of your code is a system that checks the `todos` variable, updates your Cloudflare KV cache with that value, and then does a re-render of the UI based on the data it has locally. ## 6\. Conclusion and next steps By completing this tutorial, you have built a static HTML, CSS, and JavaScript application that is transparently powered by Workers and Workers KV, which take full advantage of Cloudflare's global network. If you would like to keep improving on your project, you can implement a better design (you can refer to a live version available at [todos.signalnerve.workers.dev ↗](https://todos.signalnerve.workers.dev/)), or make additional improvements to security and speed. You may also want to add user-specific caching. Right now, the cache key is always `data` – this means that any visitor to the site will share the same todo list with other visitors. Within your Worker, you could use values from the client request to create and maintain user-specific lists. For example, you may generate a cache key based on the requesting IP: JavaScript ``` export default { async fetch(request, env, ctx) { const defaultData = { todos: [ { id: 1, name: "Finish the Cloudflare Workers blog post", completed: false, }, ], }; const setCache = (key, data) => env.TODOS.put(key, data); const getCache = (key) => env.TODOS.get(key); const ip = request.headers.get("CF-Connecting-IP"); const myKey = `data-${ip}`; if (request.method === "PUT") { const body = await request.text(); try { JSON.parse(body); await setCache(myKey, body); return new Response(body, { status: 200 }); } catch (err) { return new Response(err, { status: 500 }); } } let data; const cache = await getCache(); if (!cache) { await setCache(myKey, JSON.stringify(defaultData)); data = defaultData; } else { data = JSON.parse(cache); } const body = html(JSON.stringify(data.todos).replace(/ ` Todos

Todos

`; export default { async fetch(request, env, ctx) { const defaultData = { todos: [ { id: 1, name: "Finish the Cloudflare Workers blog post", completed: false, }, ], }; const setCache = (key, data) => env.TODOS.put(key, data); const getCache = (key) => env.TODOS.get(key); const ip = request.headers.get("CF-Connecting-IP"); const myKey = `data-${ip}`; if (request.method === "PUT") { const body = await request.text(); try { JSON.parse(body); await setCache(myKey, body); return new Response(body, { status: 200 }); } catch (err) { return new Response(err, { status: 500 }); } } let data; const cache = await getCache(); if (!cache) { await setCache(myKey, JSON.stringify(defaultData)); data = defaultData; } else { data = JSON.parse(cache); } const body = html(JSON.stringify(data.todos).replace(/QR Generator

Click the below button to generate a new QR code. This will make a request to your Worker.

Generated QR Code Image

`; ``` Explain Code The `landing` variable, which is a static HTML string, sets up an `input` tag and a corresponding `button`, which calls the `generateQRCode` function. This function will make an HTTP `POST` request back to your Worker, allowing you to see the corresponding QR code image returned on the page. With the above steps complete, your Worker is ready. The full version of the code looks like this: JavaScript ``` const QRCode = require("qrcode-svg"); export default { async fetch(request, env, ctx) { if (request.method === "POST") { return generateQRCode(request); } return new Response(landing, { headers: { "Content-Type": "text/html", }, }); }, }; async function generateQRCode(request) { const { text } = await request.json(); const qr = new QRCode({ content: text || "https://workers.dev" }); return new Response(qr.svg(), { headers: { "Content-Type": "image/svg+xml" }, }); } const landing = `

QR Generator

Click the below button to generate a new QR code. This will make a request to your Worker.

Generated QR Code Image

`; ``` Explain Code ## 5\. Deploy your Worker With all the above steps complete, you have written the code for a QR code generator on Cloudflare Workers. Wrangler has built-in support for bundling, uploading, and releasing your Cloudflare Workers application. To do this, run `npx wrangler deploy`, which will build and deploy your code. Deploy your Worker project ``` npx wrangler deploy ``` ## Related resources In this tutorial, you built and deployed a Worker application for generating QR codes. If you would like to see the full source code for this application, you can find it [on GitHub ↗](https://github.com/kristianfreeman/workers-qr-code-generator). If you want to get started building your own projects, review the existing list of [Quickstart templates](https://developers.cloudflare.com/workers/get-started/quickstarts/). ```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/tutorials/","name":"Tutorials"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/tutorials/build-a-qr-code-generator/","name":"Build a QR code generator"}}]} ``` --- --- title: Build a Slackbot description: Learn how to build a Slackbot with Hono and TypeScript in Cloudflare Workers image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) ### Tags [ Hono ](https://developers.cloudflare.com/search/?tags=Hono)[ TypeScript ](https://developers.cloudflare.com/search/?tags=TypeScript) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/tutorials/build-a-slackbot.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Build a Slackbot **Last reviewed:** almost 2 years ago In this tutorial, you will build a [Slack ↗](https://slack.com) bot using [Cloudflare Workers](https://developers.cloudflare.com/workers/). Your bot will make use of GitHub webhooks to send messages to a Slack channel when issues are updated or created, and allow users to write a command to look up GitHub issues from inside Slack. ![After following this tutorial, you will be able to create a Slackbot like the one in this example. Continue reading to build your Slackbot.](https://developers.cloudflare.com/_astro/issue-command.BJRwbx5d_Z1dTC4D.webp) This tutorial is recommended for people who are familiar with writing web applications. You will use TypeScript as the programming language and [Hono ↗](https://hono.dev/) as the web framework. If you have built an application with tools like [Node ↗](https://nodejs.org) and [Express ↗](https://expressjs.com), this project will feel very familiar to you. If you are new to writing web applications or have wanted to build something like a Slack bot in the past, but were intimidated by deployment or configuration, Workers will be a way for you to focus on writing code and shipping projects. If you would like to review the code or how the bot works in an actual Slack channel before proceeding with this tutorial, you can access the final version of the codebase [on GitHub ↗](https://github.com/yusukebe/workers-slack-bot). From GitHub, you can add your own Slack API keys and deploy it to your own Slack channels for testing. --- ## Before you start All of the tutorials assume you have already completed the [Get started guide](https://developers.cloudflare.com/workers/get-started/guide/), which gets you set up with a Cloudflare Workers account, [C3 ↗](https://github.com/cloudflare/workers-sdk/tree/main/packages/create-cloudflare), and [Wrangler](https://developers.cloudflare.com/workers/wrangler/install-and-update/). ## Set up Slack This tutorial assumes that you already have a Slack account, and the ability to create and manage Slack applications. ### Configure a Slack application To post messages from your Cloudflare Worker into a Slack channel, you will need to create an application in Slack’s UI. To do this, go to Slack’s API section, at [api.slack.com/apps ↗](https://api.slack.com/apps), and select **Create New App**. ![To create a Slackbot, first create a Slack App](https://developers.cloudflare.com/_astro/create-a-slack-app.D5_bKo4M_2uKImL.webp) Slack applications have many features. You will make use of two of them, Incoming Webhooks and Slash Commands, to build your Worker-powered Slack bot. #### Incoming Webhook Incoming Webhooks are URLs that you can use to send messages to your Slack channels. Your incoming webhook will be paired with GitHub’s webhook support to send messages to a Slack channel whenever there are updates to issues in a given repository. You will see the code in more detail as you build your application. First, create a Slack webhook: 1. On the sidebar of Slack's UI, select **Incoming Webhooks**. 2. In **Webhook URLs for your Workspace**, select **Add New Webhook to Workspace**. 3. On the following screen, select the channel that you want your webhook to send messages to (you can select a room, like #general or #code, or be messaged directly by your Slack bot when the webhook is called.) 4. Authorize the new webhook URL. After authorizing your webhook URL, you will be returned to the **Incoming Webhooks** page and can view your new webhook URL. You will add this into your Workers code later. Next, you will add the second component to your Slack bot: a Slash Command. ![Select Add New Webhook to Workspace to add a new Webhook URL in Slack's dashboard](https://developers.cloudflare.com/_astro/slack-incoming-webhook.DWpFxzq__1i7jiW.webp) #### Slash Command A Slash Command in Slack is a custom-configured command that can be attached to a URL request. For example, if you configured `/weather `, Slack would make an HTTP POST request to a configured URL, passing the text `` to get the weather for a specified zip code. In your application, you will use the `/issue` command to look up GitHub issues using the [GitHub API ↗](https://developer.github.com). Typing `/issue cloudflare/wrangler#1` will send the text `cloudflare/wrangler#1` in a HTTP POST request to your application, which the application will use to find the [relevant GitHub issue ↗](https://github.com/cloudflare/wrangler-legacy/issues/1). 1. On the Slack sidebar, select **Slash Commands**. 2. Create your first slash command. For this tutorial, you will use the command `/issue`. The request URL should be the `/lookup` path on your application URL: for example, if your application will be hosted at `https://myworkerurl.com`, the Request URL should be `https://myworkerurl.com/lookup`. ![You must create a Slash Command in Slack's dashboard and attach it to a Request URL](https://developers.cloudflare.com/_astro/create-slack-command.CBy2ieO7_Z1W4NaQ.webp) ### Configure your GitHub Webhooks Your Cloudflare Workers application will be able to handle incoming requests from Slack. It should also be able to receive events directly from GitHub. If a GitHub issue is created or updated, you can make use of GitHub webhooks to send that event to your Workers application and post a corresponding message in Slack. To configure a webhook: 1. Go to your GitHub repository's **Settings** \> **Webhooks** \> **Add webhook**. If you have a repository like `https://github.com/user/repo`, you can access the **Webhooks** page directly at `https://github.com/user/repo/settings/hooks`. 1. Set the Payload URL to the `/webhook` path on your Worker URL. For example, if your Worker will be hosted at `https://myworkerurl.com`, the Payload URL should be `https://myworkerurl.com/webhook`. 1. In the **Content type** dropdown, select **application/json**. The **Content type** for your payload can either be a URL-encoded payload (`application/x-www-form-urlencoded`) or JSON (`application/json`). For the purpose of this tutorial and to make parsing the payload sent to your application, select JSON. 1. In **Which events would you like to trigger this webhook?**, select **Let me select individual events**. GitHub webhooks allow you to specify which events you would like to have sent to your webhook. By default, the webhook will send `push` events from your repository. For the purpose of this tutorial, you will choose **Let me select individual events**. 1. Select the **Issues** event type. There are many different event types that can be enabled for your webhook. Selecting **Issues** will send every issue-related event to your webhook, including when issues are opened, edited, deleted, and more. If you would like to expand your Slack bot application in the future, you can select more of these events after the tutorial. 1. Select **Add webhook**. ![Create a GitHub Webhook in the GitHub dashboard](https://developers.cloudflare.com/_astro/new-github-webhook.DtHDy8MC_1V7hhX.webp) When your webhook is created, it will attempt to send a test payload to your application. Since your application is not actually deployed yet, leave the configuration as it is. You will later return to your repository to create, edit, and close some issues to ensure that the webhook is working once your application is deployed. ## Init To initiate the project, use the command line interface [C3 (create-cloudflare-cli) ↗](https://github.com/cloudflare/workers-sdk/tree/main/packages/create-cloudflare). npm yarn pnpm ``` npm create cloudflare@latest -- slack-bot ``` ``` yarn create cloudflare slack-bot ``` ``` pnpm create cloudflare@latest slack-bot ``` Follow these steps to create a Hono project. * For _What would you like to start with_?, select `Framework Starter`. * For _Which development framework do you want to use?_, select `Hono`. * For, _Do you want to deploy your application?_, select `No`. Go to the `slack-bot` directory: Terminal window ``` cd slack-bot ``` Open `src/index.ts` in an editor to find the following code. TypeScript ``` import { Hono } from "hono"; type Bindings = { [key in keyof CloudflareBindings]: CloudflareBindings[key]; }; const app = new Hono<{ Bindings: Bindings }>(); app.get("/", (c) => { return c.text("Hello Hono!"); }); export default app; ``` Explain Code This is a minimal application using Hono. If a GET access comes in on the path `/`, it will return a response with the text `Hello Hono!`. It also returns a message `404 Not Found` with status code 404 if any other path or method is accessed. To run the application on your local machine, execute the following command. npm yarn pnpm bun ``` npm i -- dev ``` ``` yarn add dev ``` ``` pnpm add dev ``` ``` bun add dev ``` Access to `http://localhost:8787` in your browser after the server has been started, and you can see the message. Hono helps you to create your Workers application easily and quickly. ## Build Now, let's create a Slack bot on Cloudflare Workers. ### Separating files You can create your application in several files instead of writing all endpoints and functions in one file. With Hono, it is able to add routing of child applications to the parent application using the function `app.route()`. For example, imagine the following Web API application. TypeScript ``` import { Hono } from "hono"; const app = new Hono(); app.get("/posts", (c) => c.text("Posts!")); app.post("/posts", (c) => c.text("Created!", 201)); export default app; ``` You can add the routes under `/api/v1`. TypeScript ``` import { Hono } from "hono"; import api from "./api"; const app = new Hono(); app.route("/api/v1", api); export default app; ``` It will return `Posts!` when accessing `GET /api/v1/posts`. The Slack bot will have two child applications called "route" each. 1. `lookup` route will take requests from Slack (sent when a user uses the `/issue` command), and look up the corresponding issue using the GitHub API. This application will be added to `/lookup` in the main application. 2. `webhook` route will be called when an issue changes on GitHub, via a configured webhook. This application will be add to `/webhook` in the main application. Create the route files in a directory named `routes`. Create new folders and files ``` mkdir -p src/routes touch src/routes/lookup.ts touch src/routes/webhook.ts ``` Then update the main application. TypeScript ``` import { Hono } from "hono"; import lookup from "./routes/lookup"; import webhook from "./routes/webhook"; const app = new Hono(); app.route("/lookup", lookup); app.route("/webhook", webhook); export default app; ``` Explain Code ### Defining TypeScript types Before implementing the actual functions, you need to define the TypeScript types you will use in this project. Create a new file in the application at `src/types.ts` and write the code. `Bindings` is a type that describes the Cloudflare Workers environment variables. `Issue` is a type for a GitHub issue and `User` is a type for a GitHub user. You will need these later. TypeScript ``` export type Bindings = { SLACK_WEBHOOK_URL: string; }; export type Issue = { html_url: string; title: string; body: string; state: string; created_at: string; number: number; user: User; }; type User = { html_url: string; login: string; avatar_url: string; }; ``` Explain Code ### Creating the lookup route Start creating the lookup route in `src/routes/lookup.ts`. TypeScript ``` import { Hono } from "hono"; const app = new Hono(); export default app; ``` To understand how you should design this function, you need to understand how Slack slash commands send data to URLs. According to the [documentation for Slack slash commands ↗](https://api.slack.com/interactivity/slash-commands), Slack sends an HTTP POST request to your specified URL, with a `application/x-www-form-urlencoded` content type. For example, if someone were to type `/issue cloudflare/wrangler#1`, you could expect a data payload in the format: ``` token=gIkuvaNzQIHg97ATvDxqgjtO &team_id=T0001 &team_domain=example &enterprise_id=E0001 &enterprise_name=Globular%20Construct%20Inc &channel_id=C2147483705 &channel_name=test &user_id=U2147483697 &user_name=Steve &command=/issue &text=cloudflare/wrangler#1 &response_url=https://hooks.slack.com/commands/1234/5678 &trigger_id=13345224609.738474920.8088930838d88f008e0 ``` Explain Code Given this payload body, you need to parse it, and get the value of the `text` key. With that `text`, for example, `cloudflare/wrangler#1`, you can parse that string into known piece of data (`owner`, `repo`, and `issue_number`), and use it to make a request to GitHub’s API, to retrieve the issue data. With Slack slash commands, you can respond to a slash command by returning structured data as the response to the incoming slash command. In this case, you should use the response from GitHub’s API to present a formatted version of the GitHub issue, including pieces of data like the title of the issue, who created it, and the date it was created. Slack’s new [Block Kit ↗](https://api.slack.com/block-kit) framework will allow you to return a detailed message response, by constructing text and image blocks with the data from GitHub’s API. #### Parsing slash commands To begin, the `lookup` route should parse the messages coming from Slack. As previously mentioned, the Slack API sends an HTTP POST in URL Encoded format. You can get the variable `text` by parsing it with `c.req.json()`. TypeScript ``` import { Hono } from "hono"; const app = new Hono(); app.post("/", async (c) => { const { text } = await c.req.parseBody(); if (typeof text !== "string") { return c.notFound(); } }); export default app; ``` Explain Code Given a `text` variable, that contains text like `cloudflare/wrangler#1`, you should parse that text, and get the individual parts from it for use with GitHub’s API: `owner`, `repo`, and `issue_number`. To do this, create a new file in your application, at `src/utils/github.ts`. This file will contain a number of “utility” functions for working with GitHub’s API. The first of these will be a string parser, called `parseGhIssueString`: TypeScript ``` const ghIssueRegex = /(?[\w.-]*)\/(?[\w.-]*)\#(?\d*)/; export const parseGhIssueString = (text: string) => { const match = text.match(ghIssueRegex); return match ? (match.groups ?? {}) : {}; }; ``` `parseGhIssueString` takes in a `text` input, matches it against `ghIssueRegex`, and if a match is found, returns the `groups` object from that match, making use of the `owner`, `repo`, and `issue_number` capture groups defined in the regex. By exporting this function from `src/utils/github.ts`, you can make use of it back in `src/handlers/lookup.ts`: TypeScript ``` import { Hono } from "hono"; import { parseGhIssueString } from "../utils/github"; const app = new Hono(); app.post("/", async (c) => { const { text } = await c.req.parseBody(); if (typeof text !== "string") { return c.notFound(); } const { owner, repo, issue_number } = parseGhIssueString(text); }); export default app; ``` Explain Code #### Making requests to GitHub’s API With this data, you can make your first API lookup to GitHub. Again, make a new function in `src/utils/github.ts`, to make a `fetch` request to the GitHub API for the issue data: TypeScript ``` const ghIssueRegex = /(?[\w.-]*)\/(?[\w.-]*)\#(?\d*)/; export const parseGhIssueString = (text: string) => { const match = text.match(ghIssueRegex); return match ? (match.groups ?? {}) : {}; }; export const fetchGithubIssue = ( owner: string, repo: string, issue_number: string, ) => { const url = `https://api.github.com/repos/${owner}/${repo}/issues/${issue_number}`; const headers = { "User-Agent": "simple-worker-slack-bot" }; return fetch(url, { headers }); }; ``` Explain Code Back in `src/handlers/lookup.ts`, use `fetchGitHubIssue` to make a request to GitHub’s API, and parse the response: TypeScript ``` import { Hono } from "hono"; import { fetchGithubIssue, parseGhIssueString } from "../utils/github"; import { Issue } from "../types"; const app = new Hono(); app.post("/", async (c) => { const { text } = await c.req.parseBody(); if (typeof text !== "string") { return c.notFound(); } const { owner, repo, issue_number } = parseGhIssueString(text); const response = await fetchGithubIssue(owner, repo, issue_number); const issue = await response.json(); }); export default app; ``` Explain Code #### Constructing a Slack message After you have received a response back from GitHub’s API, the final step is to construct a Slack message with the issue data, and return it to the user. The final result will look something like this: ![A successful Slack Message will have the components listed below](https://developers.cloudflare.com/_astro/issue-slack-message.8mahQ-Ir_Rfht0.webp) You can see four different pieces in the above screenshot: 1. The first line (bolded) links to the issue, and shows the issue title 2. The following lines (including code snippets) are the issue body 3. The last line of text shows the issue status, the issue creator (with a link to the user’s GitHub profile), and the creation date for the issue 4. The profile picture of the issue creator, on the right-hand side The previously mentioned [Block Kit ↗](https://api.slack.com/block-kit) framework will help take the issue data (in the structure lined out in [GitHub’s REST API documentation ↗](https://developer.github.com/v3/issues/)) and format it into something like the above screenshot. Create another file, `src/utils/slack.ts`, to contain the function `constructGhIssueSlackMessage`, a function for taking issue data, and turning it into a collection of blocks. Blocks are JavaScript objects that Slack will use to format the message: TypeScript ``` import { Issue } from "../types"; export const constructGhIssueSlackMessage = ( issue: Issue, issue_string: string, prefix_text?: string, ) => { const issue_link = `<${issue.html_url}|${issue_string}>`; const user_link = `<${issue.user.html_url}|${issue.user.login}>`; const date = new Date(Date.parse(issue.created_at)).toLocaleDateString(); const text_lines = [ prefix_text, `*${issue.title} - ${issue_link}*`, issue.body, `*${issue.state}* - Created by ${user_link} on ${date}`, ]; }; ``` Explain Code Slack messages accept a variant of Markdown, which supports bold text via asterisks (`*bolded text*`), and links in the format ``. Given that format, construct `issue_link`, which takes the `html_url` property from the GitHub API `issue` data (in format `https://github.com/cloudflare/wrangler-legacy/issues/1`), and the `issue_string` sent from the Slack slash command, and combines them into a clickable link in the Slack message. `user_link` is similar, using `issue.user.html_url` (in the format `https://github.com/signalnerve`, a GitHub user) and the user’s GitHub username (`issue.user.login`), to construct a clickable link to the GitHub user. Finally, parse `issue.created_at`, an ISO 8601 string, convert it into an instance of a JavaScript `Date`, and turn it into a formatted string, in the format `MM/DD/YY`. With those variables in place, `text_lines` is an array of each line of text for the Slack message. The first line is the **issue title** and the **issue link**, the second is the **issue body**, and the final line is the **issue state** (for example, open or closed), the **user link**, and the **creation date**. With the text constructed, you can finally construct your Slack message, returning an array of blocks for Slack’s [Block Kit ↗](https://api.slack.com/block-kit). In this case, there is only have one block: a [section ↗](https://api.slack.com/reference/messaging/blocks#section) block with Markdown text, and an accessory image of the user who created the issue. Return that single block inside of an array, to complete the `constructGhIssueSlackMessage` function: TypeScript ``` import { Issue } from "../types"; export const constructGhIssueSlackMessage = ( issue: Issue, issue_string: string, prefix_text?: string, ) => { const issue_link = `<${issue.html_url}|${issue_string}>`; const user_link = `<${issue.user.html_url}|${issue.user.login}>`; const date = new Date(Date.parse(issue.created_at)).toLocaleDateString(); const text_lines = [ prefix_text, `*${issue.title} - ${issue_link}*`, issue.body, `*${issue.state}* - Created by ${user_link} on ${date}`, ]; return [ { type: "section", text: { type: "mrkdwn", text: text_lines.join("\n"), }, accessory: { type: "image", image_url: issue.user.avatar_url, alt_text: issue.user.login, }, }, ]; }; ``` Explain Code #### Finishing the lookup route In `src/handlers/lookup.ts`, use `constructGhIssueSlackMessage` to construct `blocks`, and return them as a new response with `c.json()` when the slash command is called: TypeScript ``` import { Hono } from "hono"; import { fetchGithubIssue, parseGhIssueString } from "../utils/github"; import { constructGhIssueSlackMessage } from "../utils/slack"; import { Issue } from "../types"; const app = new Hono(); app.post("/", async (c) => { const { text } = await c.req.parseBody(); if (typeof text !== "string") { return c.notFound(); } const { owner, repo, issue_number } = parseGhIssueString(text); const response = await fetchGithubIssue(owner, repo, issue_number); const issue = await response.json(); const blocks = constructGhIssueSlackMessage(issue, text); return c.json({ blocks, response_type: "in_channel", }); }); export default app; ``` Explain Code One additional parameter passed into the response is `response_type`. By default, responses to slash commands are ephemeral, meaning that they are only seen by the user who writes the slash command. Passing a `response_type` of `in_channel`, as seen above, will cause the response to appear for all users in the channel. If you would like the messages to remain private, remove the `response_type` line. This will cause `response_type` to default to `ephemeral`. #### Handling errors The `lookup` route is almost complete, but there are a number of errors that can occur in the route, such as parsing the body from Slack, getting the issue from GitHub, or constructing the Slack message itself. Although Hono applications can handle errors without having to do anything, you can customize the response returned in the following way. TypeScript ``` import { Hono } from "hono"; import { fetchGithubIssue, parseGhIssueString } from "../utils/github"; import { constructGhIssueSlackMessage } from "../utils/slack"; import { Issue } from "../types"; const app = new Hono(); app.post("/", async (c) => { const { text } = await c.req.parseBody(); if (typeof text !== "string") { return c.notFound(); } const { owner, repo, issue_number } = parseGhIssueString(text); const response = await fetchGithubIssue(owner, repo, issue_number); const issue = await response.json(); const blocks = constructGhIssueSlackMessage(issue, text); return c.json({ blocks, response_type: "in_channel", }); }); app.onError((_e, c) => { return c.text( "Uh-oh! We couldn't find the issue you provided. " + "We can only find public issues in the following format: `owner/repo#issue_number`.", ); }); export default app; ``` Explain Code ### Creating the webhook route You are now halfway through implementing the routes for your Workers application. In implementing the next route, `src/routes/webhook.ts`, you will re-use a lot of the code that you have already written for the lookup route. At the beginning of this tutorial, you configured a GitHub webhook to track any events related to issues in your repository. When an issue is opened, for example, the function corresponding to the path `/webhook` on your Workers application should take the data sent to it from GitHub, and post a new message in the configured Slack channel. In `src/routes/webhook.ts`, define a blank Hono application. The difference from the `lookup` route is that the `Bindings` is passed as a generics for the `new Hono()`. This is necessary to give the appropriate TypeScript type to `SLACK_WEBHOOK_URL` which will be used later. TypeScript ``` import { Hono } from "hono"; import { Bindings } from "../types"; const app = new Hono<{ Bindings: Bindings }>(); export default app; ``` Much like with the `lookup` route, you will need to parse the incoming payload inside of `request`, get the relevant issue data from it (refer to [the GitHub API documentation on IssueEvent ↗](https://developer.github.com/v3/activity/events/types/#issuesevent) for the full payload schema), and send a formatted message to Slack to indicate what has changed. The final version will look something like this: ![A successful Webhook Message example](https://developers.cloudflare.com/_astro/webhook_example.EQJW9q2u_ZBVQ2l.webp) Compare this message format to the format returned when a user uses the `/issue` slash command. You will see that there is only one actual difference between the two: the addition of an action text on the first line, in the format `An issue was $action:`. This action, which is sent as part of the `IssueEvent` from GitHub, will be used as you construct a very familiar looking collection of blocks using Slack’s Block Kit. #### Parsing event data To start filling out the route, parse the request body formatted JSON into an object and construct some helper variables: TypeScript ``` import { Hono } from "hono"; import { constructGhIssueSlackMessage } from "../utils/slack"; const app = new Hono(); app.post("/", async (c) => { const { action, issue, repository } = await c.req.json(); const prefix_text = `An issue was ${action}:`; const issue_string = `${repository.owner.login}/${repository.name}#${issue.number}`; }); export default app; ``` Explain Code An `IssueEvent`, the payload sent from GitHub as part of your webhook configuration, includes an `action` (what happened to the issue: for example, it was opened, closed, locked, etc.), the `issue` itself, and the `repository`, among other things. Use `c.req.json()` to convert the payload body of the request from JSON into a plain JS object. Use ES6 destructuring to set `action`, `issue` and `repository` as variables you can use in your code. `prefix_text` is a string indicating what happened to the issue, and `issue_string` is the familiar string `owner/repo#issue_number` that you have seen before: while the `lookup` route directly used the text sent from Slack to fill in `issue_string`, you will construct it directly based on the data passed in the JSON payload. #### Constructing and sending a Slack message The messages your Slack bot sends back to your Slack channel from the `lookup` and `webhook` routes are incredibly similar. Because of this, you can re-use the existing `constructGhIssueSlackMessage` to continue populating `src/handlers/webhook.ts`. Import the function from `src/utils/slack.ts`, and pass the issue data into it: TypeScript ``` import { Hono } from "hono"; import { constructGhIssueSlackMessage } from "../utils/slack"; const app = new Hono(); app.post("/", async (c) => { const { action, issue, repository } = await c.req.json(); const prefix_text = `An issue was ${action}:`; const issue_string = `${repository.owner.login}/${repository.name}#${issue.number}`; const blocks = constructGhIssueSlackMessage(issue, issue_string, prefix_text); }); export default app; ``` Explain Code Importantly, the usage of `constructGhIssueSlackMessage` in this handler adds one additional argument to the function, `prefix_text`. Update the corresponding function inside of `src/utils/slack.ts`, adding `prefix_text` to the collection of `text_lines` in the message block, if it has been passed in to the function. Add a utility function, `compact`, which takes an array, and filters out any `null` or `undefined` values from it. This function will be used to remove `prefix_text` from `text_lines` if it has not actually been passed in to the function, such as when called from `src/handlers/lookup.ts`. The full (and final) version of the `src/utils/slack.ts` looks like this: TypeScript ``` import { Issue } from "../types"; const compact = (array: unknown[]) => array.filter((el) => el); export const constructGhIssueSlackMessage = ( issue: Issue, issue_string: string, prefix_text?: string, ) => { const issue_link = `<${issue.html_url}|${issue_string}>`; const user_link = `<${issue.user.html_url}|${issue.user.login}>`; const date = new Date(Date.parse(issue.created_at)).toLocaleDateString(); const text_lines = [ prefix_text, `*${issue.title} - ${issue_link}*`, issue.body, `*${issue.state}* - Created by ${user_link} on ${date}`, ]; return [ { type: "section", text: { type: "mrkdwn", text: compact(text_lines).join("\n"), }, accessory: { type: "image", image_url: issue.user.avatar_url, alt_text: issue.user.login, }, }, ]; }; ``` Explain Code Back in `src/handlers/webhook.ts`, the `blocks` that are returned from `constructGhIssueSlackMessage` become the body in a new `fetch` request, an HTTP POST request to a Slack webhook URL. Once that request completes, return a response with status code `200`, and the body text `"OK"`: TypeScript ``` import { Hono } from "hono"; import { constructGhIssueSlackMessage } from "../utils/slack"; import { Bindings } from "../types"; const app = new Hono<{ Bindings: Bindings }>(); app.post("/", async (c) => { const { action, issue, repository } = await c.req.json(); const prefix_text = `An issue was ${action}:`; const issue_string = `${repository.owner.login}/${repository.name}#${issue.number}`; const blocks = constructGhIssueSlackMessage(issue, issue_string, prefix_text); const fetchResponse = await fetch(c.env.SLACK_WEBHOOK_URL, { body: JSON.stringify({ blocks }), method: "POST", headers: { "Content-Type": "application/json" }, }); return c.text("OK"); }); export default app; ``` Explain Code The constant `SLACK_WEBHOOK_URL` represents the Slack Webhook URL that you created all the way back in the [Incoming Webhook](https://developers.cloudflare.com/workers/tutorials/build-a-slackbot/#incoming-webhook) section of this tutorial. Warning Since this webhook allows developers to post directly to your Slack channel, keep it secret. To use this constant inside of your codebase, use the [wrangler secret](https://developers.cloudflare.com/workers/wrangler/commands/general/#secret) command: Set the SLACK\_WEBHOOK\_URL secret ``` npx wrangler secret put SLACK_WEBHOOK_URL ``` ``` Enter a secret value: https://hooks.slack.com/services/abc123 ``` #### Handling errors Similarly to the `lookup` route, the `webhook` route should include some basic error handling. Unlike `lookup`, which sends responses directly back into Slack, if something goes wrong with your webhook, it may be useful to actually generate an erroneous response, and return it to GitHub. To do this, write the custom error handler with `app.onError()` and return a new response with a status code of `500`. The final version of `src/routes/webhook.ts` looks like this: TypeScript ``` import { Hono } from "hono"; import { constructGhIssueSlackMessage } from "../utils/slack"; import { Bindings } from "../types"; const app = new Hono<{ Bindings: Bindings }>(); app.post("/", async (c) => { const { action, issue, repository } = await c.req.json(); const prefix_text = `An issue was ${action}:`; const issue_string = `${repository.owner.login}/${repository.name}#${issue.number}`; const blocks = constructGhIssueSlackMessage(issue, issue_string, prefix_text); const fetchResponse = await fetch(c.env.SLACK_WEBHOOK_URL, { body: JSON.stringify({ blocks }), method: "POST", headers: { "Content-Type": "application/json" }, }); if (!fetchResponse.ok) throw new Error(); return c.text("OK"); }); app.onError((_e, c) => { return c.json( { message: "Unable to handle webhook", }, 500, ); }); export default app; ``` Explain Code ## Deploy By completing the preceding steps, you have finished writing the code for your Slack bot. You can now deploy your application. Wrangler has built-in support for bundling, uploading, and releasing your Cloudflare Workers application. To do this, run the following command which will build and deploy your code. npm yarn pnpm bun ``` npm i -- deploy ``` ``` yarn add deploy ``` ``` pnpm add deploy ``` ``` bun add deploy ``` Deploying your Workers application should now cause issue updates to start appearing in your Slack channel, as the GitHub webhook can now successfully reach your Workers webhook route: ![When you create new issue, a Slackbot will now appear in your Slack channel](https://developers.cloudflare.com/images/workers/tutorials/slackbot/create-new-issue.gif) ## Related resources In this tutorial, you built and deployed a Cloudflare Workers application that can respond to GitHub webhook events, and allow GitHub API lookups within Slack. If you would like to review the full source code for this application, you can find the repository [on GitHub ↗](https://github.com/yusukebe/workers-slack-bot). If you want to get started building your own projects, review the existing list of [Quickstart templates](https://developers.cloudflare.com/workers/get-started/quickstarts/). ```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/tutorials/","name":"Tutorials"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/tutorials/build-a-slackbot/","name":"Build a Slackbot"}}]} ``` --- --- title: Connect to and query your Turso database using Workers description: This tutorial will guide you on how to build globally distributed applications with Cloudflare Workers, and Turso, an edge-hosted distributed database based on libSQL. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) ### Tags [ TypeScript ](https://developers.cloudflare.com/search/?tags=TypeScript)[ SQL ](https://developers.cloudflare.com/search/?tags=SQL) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/tutorials/connect-to-turso-using-workers.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Connect to and query your Turso database using Workers **Last reviewed:** about 3 years ago This tutorial will guide you on how to build globally distributed applications with Cloudflare Workers, and [Turso ↗](https://chiselstrike.com/), an edge-hosted distributed database based on libSQL. By using Workers and Turso, you can create applications that are close to your end users without having to maintain or operate infrastructure in tens or hundreds of regions. Note For a more seamless experience, refer to the [Turso Database Integration guide](https://developers.cloudflare.com/workers/databases/third-party-integrations/turso/). The Turso Database Integration will guide you through connecting your Worker to a Turso database by securely configuring your database credentials as [secrets](https://developers.cloudflare.com/workers/configuration/secrets/) in your Worker. ## Prerequisites Before continuing with this tutorial, you should have: * Successfully [created up your first Cloudflare Worker](https://developers.cloudflare.com/workers/get-started/guide/) and/or have deployed a Cloudflare Worker before. * Installed [Wrangler](https://developers.cloudflare.com/workers/wrangler/install-and-update/), a command-line tool for building Cloudflare Workers. * A [GitHub account ↗](https://github.com/), required for authenticating to Turso. * A basic familiarity with installing and using command-line interface (CLI) applications. ## Install the Turso CLI You will need the Turso CLI to create and populate a database. Run either of the following two commands in your terminal to install the Turso CLI: Terminal window ``` # On macOS or Linux with Homebrew brew install chiselstrike/tap/turso # Manual scripted installation curl -sSfL | bash ``` After you have installed the Turso CLI, verify that the CLI is in your shell path: Terminal window ``` turso --version ``` ``` # This should output your current Turso CLI version (your installed version may be higher): turso version v0.51.0 ``` ## Create and populate a database Before you create your first Turso database, you need to log in to the CLI using your GitHub account by running: Terminal window ``` turso auth login ``` ``` Waiting for authentication... ✔ Success! Logged in as ``` `turso auth login` will open a browser window and ask you to sign into your GitHub account, if you are not already logged in. The first time you do this, you will need to give the Turso application permission to use your account. Select **Approve** to grant Turso the permissions needed. After you have authenticated, you can create a database by running `turso db create `. Turso will automatically choose a location closest to you. Terminal window ``` turso db create my-db ``` ``` # Example: [===> ] Creating database my-db in Los Angeles, California (US) (lax) # Once succeeded: Created database my-db in Los Angeles, California (US) (lax) in 34 seconds. ``` With your first database created, you can now connect to it directly and execute SQL against it: Terminal window ``` turso db shell my-db ``` To get started with your database, create and define a schema for your first table. In this example, you will create a `example_users` table with one column: `email` (of type `text`) and then populate it with one email address. In the shell you just opened, paste in the following SQL: ``` create table example_users (email text); insert into example_users values ('foo@bar.com'); ``` If the SQL statements succeeded, there will be no output. Note that the trailing semi-colons (`;`) are necessary to terminate each SQL statement. Type `.quit` to exit the shell. ## Use Wrangler to create a Workers project The Workers command-line interface, [Wrangler](https://developers.cloudflare.com/workers/wrangler/install-and-update/), allows you to create, locally develop, and deploy your Workers projects. To create a new Workers project (named `worker-turso-ts`), run the following: npm yarn pnpm ``` npm create cloudflare@latest -- worker-turso-ts ``` ``` yarn create cloudflare worker-turso-ts ``` ``` pnpm create cloudflare@latest worker-turso-ts ``` For setup, select the following options: * For _What would you like to start with?_, choose `Hello World example`. * For _Which template would you like to use?_, choose `Worker only`. * For _Which language do you want to use?_, choose `TypeScript`. * For _Do you want to use git for version control?_, choose `Yes`. * For _Do you want to deploy your application?_, choose `No` (we will be making some changes before deploying). To start developing your Worker, `cd` into your new project directory: Terminal window ``` cd worker-turso-ts ``` In your project directory, you now have the following files: * `wrangler.json` / `wrangler.toml`: [Wrangler configuration file](https://developers.cloudflare.com/workers/wrangler/configuration/) * `src/index.ts`: A minimal Hello World Worker written in TypeScript * `package.json`: A minimal Node dependencies configuration file. * `tsconfig.json`: TypeScript configuration that includes Workers types. Only generated if indicated. For this tutorial, only the [Wrangler configuration file](https://developers.cloudflare.com/workers/wrangler/configuration/) and `src/index.ts` file are relevant. You will not need to edit the other files, and they should be left as is. ## Configure your Worker for your Turso database The Turso client library requires two pieces of information to make a connection: 1. `LIBSQL_DB_URL` \- The connection string for your Turso database. 2. `LIBSQL_DB_AUTH_TOKEN` \- The authentication token for your Turso database. This should be kept a secret, and not committed to source code. To get the URL for your database, run the following Turso CLI command, and copy the result: Terminal window ``` turso db show my-db --url ``` ``` libsql://my-db-.turso.io ``` Open the [Wrangler configuration file](https://developers.cloudflare.com/workers/wrangler/configuration/) in your editor and at the bottom of the file, create a new `[vars]` section representing the [environment variables](https://developers.cloudflare.com/workers/configuration/environment-variables/) for your project: * [ wrangler.jsonc ](#tab-panel-7928) * [ wrangler.toml ](#tab-panel-7929) JSONC ``` { "vars": { "LIBSQL_DB_URL": "paste-your-url-here" } } ``` TOML ``` [vars] LIBSQL_DB_URL = "paste-your-url-here" ``` Save the changes to the [Wrangler configuration file](https://developers.cloudflare.com/workers/wrangler/configuration/). Next, create a long-lived authentication token for your Worker to use when connecting to your database. Run the following Turso CLI command, and copy the output to your clipboard: Terminal window ``` turso db tokens create my-db -e none # Will output a long text string (an encoded JSON Web Token) ``` To keep this token secret: 1. You will create a `.dev.vars` file for local development. Do not commit this file to source control. You should add `.dev.vars to your `.gitignore\` file if you are using Git. * You will also create a [secret](https://developers.cloudflare.com/workers/configuration/secrets/) to keep your authentication token confidential. First, create a new file called `.dev.vars` with the following structure. Paste your authentication token in the quotation marks: ``` LIBSQL_DB_AUTH_TOKEN="" ``` Save your changes to `.dev.vars`. Next, store the authentication token as a secret for your production Worker to reference. Run the following `wrangler secret` command to create a Secret with your token: Terminal window ``` # Ensure you specify the secret name exactly: your Worker will need to reference it later. npx wrangler secret put LIBSQL_DB_AUTH_TOKEN ``` ``` ? Enter a secret value: › ``` Select `` on your keyboard to save the token as a secret. Both `LIBSQL_DB_URL` and `LIBSQL_DB_AUTH_TOKEN` will be available in your Worker's environment at runtime. ## Install extra libraries Install the Turso client library and a router: npm yarn pnpm bun ``` npm i @libsql/client itty-router ``` ``` yarn add @libsql/client itty-router ``` ``` pnpm add @libsql/client itty-router ``` ``` bun add @libsql/client itty-router ``` The `@libsql/client` library allows you to query a Turso database. The `itty-router` library is a lightweight router you will use to help handle incoming requests to the worker. ## Write your Worker You will now write a Worker that will: 1. Handle an HTTP request. 2. Route it to a specific handler to either list all users in our database or add a new user. 3. Return the results and/or success. Open `src/index.ts` and delete the existing template. Copy the below code exactly as is and paste it into the file: TypeScript ``` import { Client as LibsqlClient, createClient } from "@libsql/client/web"; import { Router, RouterType } from "itty-router"; export interface Env { // The environment variable containing your the URL for your Turso database. LIBSQL_DB_URL?: string; // The Secret that contains the authentication token for your Turso database. LIBSQL_DB_AUTH_TOKEN?: string; // These objects are created before first use, then stashed here // for future use router?: RouterType; } export default { async fetch(request, env): Promise { if (env.router === undefined) { env.router = buildRouter(env); } return env.router.fetch(request); }, } satisfies ExportedHandler; function buildLibsqlClient(env: Env): LibsqlClient { const url = env.LIBSQL_DB_URL?.trim(); if (url === undefined) { throw new Error("LIBSQL_DB_URL env var is not defined"); } const authToken = env.LIBSQL_DB_AUTH_TOKEN?.trim(); if (authToken === undefined) { throw new Error("LIBSQL_DB_AUTH_TOKEN env var is not defined"); } return createClient({ url, authToken }); } function buildRouter(env: Env): RouterType { const router = Router(); router.get("/users", async () => { const client = buildLibsqlClient(env); const rs = await client.execute("select * from example_users"); return Response.json(rs); }); router.get("/add-user", async (request) => { const client = buildLibsqlClient(env); const email = request.query.email; if (email === undefined) { return new Response("Missing email", { status: 400 }); } if (typeof email !== "string") { return new Response("email must be a single string", { status: 400 }); } if (email.length === 0) { return new Response("email length must be > 0", { status: 400 }); } try { await client.execute({ sql: "insert into example_users values (?)", args: [email], }); } catch (e) { console.error(e); return new Response("database insert failed"); } return new Response("Added"); }); router.all("*", () => new Response("Not Found.", { status: 404 })); return router; } ``` Explain Code Save your `src/index.ts` file with your changes. Note: * The libSQL client library import '@libsql/client/web' must be imported exactly as shown when working with Cloudflare workers. The non-web import will not work in the Workers environment. * The `Env` interface contains the environment variable and secret you defined earlier. * The `Env` interface also caches the libSQL client object and router, which are created on the first request to the Worker. * The `/users` route fetches all rows from the `example_users` table you created in the Turso shell. It simply serializes the `ResultSet` object as JSON directly to the caller. * The `/add-user` route inserts a new row using a value provided in the query string. With your environment configured and your code ready, you will now test your Worker locally before you deploy. ## Run the Worker locally with Wrangler To run a local instance of our Worker (entirely on your machine), run the following command: Terminal window ``` npx wrangler dev ``` You should be able to review output similar to the following: ``` Your worker has access to the following bindings: - Vars: - LIBSQL_DB_URL: "your-url" ⎔ Starting a local server... ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ [b] open a browser, [d] open Devtools, [l] turn off local mode, [c] clear console, [x] to exit │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ Debugger listening on ws://127.0.0.1:61918/1064babd-bc9d-4bed-b171-b35dab3b7680 For help, see: https://nodejs.org/en/docs/inspector Debugger attached. [mf:inf] Worker reloaded! (40.25KiB) [mf:inf] Listening on 0.0.0.0:8787 [mf:inf] - http://127.0.0.1:8787 [mf:inf] - http://192.168.1.136:8787 [mf:inf] Updated `Request.cf` object cache! ``` Explain Code The localhost address — the one with `127.0.0.1` in it — is a web-server running locally on your machine. Connect to it and validate your Worker returns the email address you inserted when you created your `example_users` table by visiting the `/users` route in your browser: [http://127.0.0.1:8787/users ↗](http://127.0.0.1:8787/users). You should see JSON similar to the following containing the data from the `example_users` table: ``` { "columns": ["email"], "rows": [{ "email": "foo@bar.com" }], "rowsAffected": 0 } ``` Warning If you see an error instead of a list of users, double check that: * You have entered the correct value for your `LIBSQL_DB_URL` in the [Wrangler configuration file](https://developers.cloudflare.com/workers/wrangler/configuration/). * You have set a secret called `LIBSQL_DB_AUTH_TOKEN` with your database authentication token. Both of these need to be present and match the variable names in your Worker's code. Test the `/add-users` route and pass it an email address to insert: [http://127.0.0.1:8787/add-user?email=test@test.com ↗](http://127.0.0.1:8787/add-user?email=test@test.com.) You should see the text `“Added”`. If you load the first URL with the `/users` route again ([http://127.0.0.1:8787/users ↗](http://127.0.0.1:8787/users)), it will show the newly added row. You can repeat this as many times as you like. Note that due to its design, your application will not stop you from adding duplicate email addresses. Quit Wrangler by typing `q` into the shell where it was started. ## Deploy to Cloudflare After you have validated that your Worker can connect to your Turso database, deploy your Worker. Run the following Wrangler command to deploy your Worker to the Cloudflare global network: Terminal window ``` npx wrangler deploy ``` The first time you run this command, it will launch a browser, ask you to sign in with your Cloudflare account, and grant permissions to Wrangler. The `deploy` command will output the following: ``` Your worker has access to the following bindings: - Vars: - LIBSQL_DB_URL: "your-url" ... Published worker-turso-ts (0.19 sec) https://worker-turso-ts..workers.dev Current Deployment ID: f9e6b48f-5aac-40bd-8f44-8a40be2212ff ``` You have now deployed a Worker that can connect to your Turso database, query it, and insert new data. ## Optional: Clean up To clean up the resources you created as part of this tutorial: * If you do not want to keep this Worker, run `npx wrangler delete worker-turso-ts` to delete the deployed Worker. * You can also delete your Turso database via `turso db destroy my-db`. ## Related resources * Find the [complete project source code on GitHub ↗](https://github.com/cloudflare/workers-sdk/tree/main/templates/worker-turso-ts/). * Understand how to [debug your Cloudflare Worker](https://developers.cloudflare.com/workers/observability/). * Join the [Cloudflare Developer Discord ↗](https://discord.cloudflare.com). * Join the [ChiselStrike (Turso) Discord ↗](https://discord.com/invite/4B5D7hYwub). ```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/tutorials/","name":"Tutorials"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/tutorials/connect-to-turso-using-workers/","name":"Connect to and query your Turso database using Workers"}}]} ``` --- --- title: Create a fine-tuned OpenAI model with R2 description: In this tutorial, you will use the OpenAI API and Cloudflare R2 to create a fine-tuned model. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) ### Tags [ AI ](https://developers.cloudflare.com/search/?tags=AI)[ Hono ](https://developers.cloudflare.com/search/?tags=Hono)[ TypeScript ](https://developers.cloudflare.com/search/?tags=TypeScript) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/tutorials/create-finetuned-chatgpt-ai-models-with-r2.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Create a fine-tuned OpenAI model with R2 **Last reviewed:** almost 2 years ago In this tutorial, you will use the [OpenAI ↗](https://openai.com) API and [Cloudflare R2](https://developers.cloudflare.com/r2) to create a [fine-tuned model ↗](https://platform.openai.com/docs/guides/fine-tuning). This feature in OpenAI's API allows you to derive a custom model from OpenAI's various large language models based on a set of custom instructions and example answers. These instructions and example answers are written in a document, known as a fine-tune document. This document will be stored in R2 and dynamically provided to OpenAI's APIs when creating a new fine-tune model. In order to use this feature, you will do the following tasks: 1. Upload a fine-tune document to R2. 2. Read the R2 file and upload it to OpenAI. 3. Create a new fine-tuned model based on the document. ![Demo](https://developers.cloudflare.com/_astro/finetune-example.Df8cOHyQ_1PgFLK.webp) To review the completed code for this application, refer to the [GitHub repository for this tutorial ↗](https://github.com/kristianfreeman/openai-finetune-r2-example). ## Prerequisites Before you start, make sure you have: * A Cloudflare account with access to R2\. If you do not have a Cloudflare account, [sign up ↗](https://dash.cloudflare.com/sign-up/workers-and-pages) before continuing. Then purchase R2 from your Cloudflare dashboard. * An OpenAI API key. * A fine-tune document, structured as [JSON Lines ↗](https://jsonlines.org/). Use the [example document ↗](https://github.com/kristianfreeman/openai-finetune-r2-example/blob/16ca53ca9c8589834abe317487eeedb8a24c7643/example%5Fdata.jsonl) in the source code. ## 1\. Create a Worker application First, use the `c3` CLI to create a new Cloudflare Workers project. npm yarn pnpm ``` npm create cloudflare@latest -- finetune-chatgpt-model ``` ``` yarn create cloudflare finetune-chatgpt-model ``` ``` pnpm create cloudflare@latest finetune-chatgpt-model ``` For setup, select the following options: * For _What would you like to start with?_, choose `Hello World example`. * For _Which template would you like to use?_, choose `Worker only`. * For _Which language do you want to use?_, choose `TypeScript`. * For _Do you want to use git for version control?_, choose `Yes`. * For _Do you want to deploy your application?_, choose `No` (we will be making some changes before deploying). The above options will create the "Hello World" TypeScript project. Move into your newly created directory: Terminal window ``` cd finetune-chatgpt-model ``` ## 2\. Upload a fine-tune document to R2 Next, upload the fine-tune document to R2\. R2 is a key-value store that allows you to store and retrieve files from within your Workers application. You will use [Wrangler](https://developers.cloudflare.com/workers/wrangler) to create a new R2 bucket. To create a new R2 bucket use the [wrangler r2 bucket create](https://developers.cloudflare.com/workers/wrangler/commands/r2/#r2-bucket-create) command. Note that you are logged in with your Cloudflare account. If not logged in via Wrangler, use the [wrangler login](https://developers.cloudflare.com/workers/wrangler/commands/general/#login) command. Terminal window ``` npx wrangler r2 bucket create ``` Replace `` with your desired bucket name. Note that bucket names must be lowercase and can only contain dashes. Next, upload a file using the [wrangler r2 object put](https://developers.cloudflare.com/workers/wrangler/commands/r2/#r2-object-put) command. Terminal window ``` npx wrangler r2 object put -f ``` `` is the combined bucket and file path of the file you want to upload -- for example, `fine-tune-ai/finetune.jsonl`, where `fine-tune-ai` is the bucket name. Replace `` with the local filename of your fine-tune document. ## 3\. Bind your bucket to the Worker A binding is how your Worker interacts with external resources such as the R2 bucket. To bind the R2 bucket to your Worker, add the following to your Wrangler file. Update the binding property to a valid JavaScript variable identifier. Replace `` with the name of the bucket you created in [step 2](#2-upload-a-fine-tune-document-to-r2): * [ wrangler.jsonc ](#tab-panel-7930) * [ wrangler.toml ](#tab-panel-7931) JSONC ``` { "r2_buckets": [ { "binding": "MY_BUCKET", // <~ valid JavaScript variable name "bucket_name": "" } ] } ``` TOML ``` [[r2_buckets]] binding = "MY_BUCKET" bucket_name = "" ``` ## 4\. Initialize your Worker application You will use [Hono ↗](https://hono.dev/), a lightweight framework for building Cloudflare Workers applications. Hono provides an interface for defining routes and middleware functions. Inside your project directory, run the following command to install Hono: npm yarn pnpm bun ``` npm i hono ``` ``` yarn add hono ``` ``` pnpm add hono ``` ``` bun add hono ``` You also need to install the [OpenAI Node API library ↗](https://www.npmjs.com/package/openai). This library provides convenient access to the OpenAI REST API in a Node.js project. To install the library, execute the following command: npm yarn pnpm bun ``` npm i openai ``` ``` yarn add openai ``` ``` pnpm add openai ``` ``` bun add openai ``` Next, open the `src/index.ts` file and replace the default code with the below code. Replace `` with the binding name you set in Wrangler file. TypeScript ``` import { Context, Hono } from "hono"; import OpenAI from "openai"; type Bindings = { : R2Bucket OPENAI_API_KEY: string } type Variables = { openai: OpenAI } const app = new Hono<{ Bindings: Bindings, Variables: Variables }>() app.use('*', async (c, next) => { const openai = new OpenAI({ apiKey: c.env.OPENAI_API_KEY, }) c.set("openai", openai) await next() }) app.onError((err, c) => { return c.text(err.message, 500) }) export default app; ``` Explain Code In the above code, you first import the required packages and define the types. Then, you initialize `app` as a new Hono instance. Using the `use` middleware function, you add the OpenAI API client to the context of all routes. This middleware function allows you to access the client from within any route handler. `onError()` defines an error handler to return any errors as a JSON response. ## 5\. Read R2 files and upload them to OpenAI In this section, you will define the route and function responsible for handling file uploads. In `createFile`, your Worker reads the file from R2 and converts it to a `File` object. Your Worker then uses the OpenAI API to upload the file and return the response. The `GET /files` route listens for `GET` requests with a query parameter `file`, representing a filename of an uploaded fine-tune document in R2\. The function uses the `createFile` function to manage the file upload process. Replace `` with the binding name you set in Wrangler file. TypeScript ``` // New import added at beginning of file import { toFile } from 'openai/uploads' const createFile = async (c: Context, r2Object: R2ObjectBody) => { const openai: OpenAI = c.get("openai") const blob = await r2Object.blob() const file = await toFile(blob, r2Object.key) const uploadedFile = await openai.files.create({ file, purpose: "fine-tune", }) return uploadedFile } app.get('/files', async c => { const fileQueryParam = c.req.query("file") if (!fileQueryParam) return c.text("Missing file query param", 400) const file = await c.env..get(fileQueryParam) if (!file) return c.text("Couldn't find file", 400) const uploadedFile = await createFile(c, file) return c.json(uploadedFile) }) ``` Explain Code ## 6\. Create fine-tuned models This section includes the `GET /models` route and the `createModel` function. The function `createModel` takes care of specifying the details and initiating the fine-tuning process with OpenAI. The route handles incoming requests for creating a new fine-tuned model. TypeScript ``` const createModel = async (c: Context, fileId: string) => { const openai: OpenAI = c.get("openai"); const body = { training_file: fileId, model: "gpt-4o-mini", }; return openai.fineTuning.jobs.create(body); }; app.get("/models", async (c) => { const fileId = c.req.query("file_id"); if (!fileId) return c.text("Missing file ID query param", 400); const model = await createModel(c, fileId); return c.json(model); }); ``` Explain Code ## 7\. List all fine-tune jobs This section describes the `GET /jobs` route and the corresponding `getJobs` function. The function interacts with OpenAI's API to fetch a list of all fine-tuning jobs. The route provides an interface for retrieving this information. TypeScript ``` const getJobs = async (c: Context) => { const openai: OpenAI = c.get("openai"); const resp = await openai.fineTuning.jobs.list(); return resp.data; }; app.get("/jobs", async (c) => { const jobs = await getJobs(c); return c.json(jobs); }); ``` Explain Code ## 8\. Deploy your application After you have created your Worker application and added the required functions, deploy the application. Before you deploy, you must set the `OPENAI_API_KEY` [secret](https://developers.cloudflare.com/workers/configuration/secrets/) for your application. Do this by running the [wrangler secret put](https://developers.cloudflare.com/workers/wrangler/commands/general/#secret-put) command: Terminal window ``` npx wrangler secret put OPENAI_API_KEY ``` To deploy your Worker application to the Cloudflare global network: 1. Make sure you are in your Worker project's directory, then run the [wrangler deploy](https://developers.cloudflare.com/workers/wrangler/commands/general/#deploy) command: Terminal window ``` npx wrangler deploy ``` 1. Wrangler will package and upload your code. 2. After your application is deployed, Wrangler will provide you with your Worker's URL. ## 9\. View the fine-tune job status and use the model To use your application, create a new fine-tune job by making a request to the `/files` with a `file` query param matching the filename you uploaded earlier: Terminal window ``` curl https://your-worker-url.com/files?file=finetune.jsonl ``` When the file is uploaded, issue another request to `/models`, passing the `file_id` query parameter. This should match the `id` returned as JSON from the `/files` route: Terminal window ``` curl https://your-worker-url.com/models?file_id=file-abc123 ``` Finally, visit `/jobs` to see the status of your fine-tune jobs in OpenAI. Once the fine-tune job has completed, you can see the `fine_tuned_model` value, indicating a fine-tuned model has been created. ![Jobs](https://developers.cloudflare.com/_astro/finetune-jobs.BQ_jbiJu_Z2n2Er.webp) Visit the [OpenAI Playground ↗](https://platform.openai.com/playground) in order to use your fine-tune model. Select your fine-tune model from the top-left dropdown of the interface. ![Demo](https://developers.cloudflare.com/_astro/finetune-example.Df8cOHyQ_1PgFLK.webp) Use it in any API requests you make to OpenAI's chat completions endpoints. For instance, in the below code example: JavaScript ``` openai.chat.completions.create({ messages: [{ role: "system", content: "You are a helpful assistant." }], model: "ft:gpt-4o-mini:my-org:custom_suffix:id", }); ``` ## Next steps To build more with Workers, refer to [Tutorials](https://developers.cloudflare.com/workers/tutorials). If you have any questions, need assistance, or would like to share your project, join the Cloudflare Developer community on [Discord ↗](https://discord.cloudflare.com) to connect with other developers and the Cloudflare team. ```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/tutorials/","name":"Tutorials"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/tutorials/create-finetuned-chatgpt-ai-models-with-r2/","name":"Create a fine-tuned OpenAI model with R2"}}]} ``` --- --- title: Deploy a real-time chat application description: This tutorial shows how to deploy a serverless, real-time chat application. The chat application uses a Durable Object to control each chat room. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) ### Tags [ JavaScript ](https://developers.cloudflare.com/search/?tags=JavaScript) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/tutorials/deploy-a-realtime-chat-app.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Deploy a real-time chat application **Last reviewed:** over 2 years ago In this tutorial, you will deploy a serverless, real-time chat application that runs using [Durable Objects](https://developers.cloudflare.com/durable-objects/). This chat application uses a Durable Object to control each chat room. Users connect to the Object using WebSockets. Messages from one user are broadcast to all the other users. The chat history is also stored in durable storage. Real-time messages are relayed directly from one user to others without going through the storage layer. ## Before you start All of the tutorials assume you have already completed the [Get started guide](https://developers.cloudflare.com/workers/get-started/guide/), which gets you set up with a Cloudflare Workers account, [C3 ↗](https://github.com/cloudflare/workers-sdk/tree/main/packages/create-cloudflare), and [Wrangler](https://developers.cloudflare.com/workers/wrangler/install-and-update/). ## Clone the chat application repository Open your terminal and clone the [workers-chat-demo ↗](https://github.com/cloudflare/workers-chat-demo) repository: Terminal window ``` git clone https://github.com/cloudflare/workers-chat-demo.git ``` ## Authenticate Wrangler After you have cloned the repository, authenticate Wrangler by running: Terminal window ``` npx wrangler login ``` ## Deploy your project When you are ready to deploy your application, run: Terminal window ``` npx wrangler deploy ``` Your application will be deployed to your `*.workers.dev` subdomain. To deploy your application to a custom domain within the Cloudflare dashboard, go to your Worker > **Triggers** \> **Add Custom Domain**. To deploy your application to a custom domain using Wrangler, open your project's [Wrangler configuration file](https://developers.cloudflare.com/workers/wrangler/configuration/). To configure a route in your Wrangler configuration file, add the following to your environment: * [ wrangler.jsonc ](#tab-panel-7932) * [ wrangler.toml ](#tab-panel-7933) JSONC ``` { "routes": [ { "pattern": "example.com/about", "zone_id": "" } ] } ``` TOML ``` [[routes]] pattern = "example.com/about" zone_id = "" ``` If you have specified your zone ID in the environment of your Wrangler configuration file, you will not need to write it again in object form. To configure a subdomain in your Wrangler configuration file, add the following to your environment: * [ wrangler.jsonc ](#tab-panel-7934) * [ wrangler.toml ](#tab-panel-7935) JSONC ``` { "routes": [ { "pattern": "subdomain.example.com", "custom_domain": true } ] } ``` TOML ``` [[routes]] pattern = "subdomain.example.com" custom_domain = true ``` To test your live application: 1. In the Cloudflare dashboard, go to the **Workers & Pages** page. [ Go to **Workers & Pages** ](https://dash.cloudflare.com/?to=/:account/workers-and-pages) 2. Select your Worker > **Triggers** \> **Routes** \> Select the `edge-chat-demo..workers.dev` route. 3. Enter a name in the **your name** field. 4. Choose whether to enter a public room or create a private room. 5. Send the link to other participants. You will be able to view room participants on the right side of the screen. ## Uninstall your application To uninstall your chat application, modify your Wrangler file to remove the `durable_objects` bindings and add a `deleted_classes` migration: * [ wrangler.jsonc ](#tab-panel-7936) * [ wrangler.toml ](#tab-panel-7937) JSONC ``` { "durable_objects": { "bindings": [] }, // Indicate that you want the ChatRoom and RateLimiter classes to be callable as Durable Objects. "migrations": [ { "tag": "v1", "new_sqlite_classes": [ "ChatRoom", "RateLimiter" ] }, { "tag": "v2", // Should be unique for each entry "deleted_classes": [ "ChatRoom", "RateLimiter" ] } ] } ``` Explain Code TOML ``` [durable_objects] bindings = [ ] [[migrations]] tag = "v1" new_sqlite_classes = [ "ChatRoom", "RateLimiter" ] [[migrations]] tag = "v2" deleted_classes = [ "ChatRoom", "RateLimiter" ] ``` Explain Code Then run `npx wrangler deploy`. To delete your Worker: 1. In the Cloudflare dashboard, go to the **Workers & Pages** page. [ Go to **Workers & Pages** ](https://dash.cloudflare.com/?to=/:account/workers-and-pages) 2. In **Overview**, select your Worker. 3. Select **Manage Service** \> **Delete**. For complete instructions on set up and deletion, refer to the `README.md` in your cloned repository. By completing this tutorial, you have deployed a real-time chat application with Durable Objects and Cloudflare Workers. ## Related resources Continue building with other Cloudflare Workers tutorials below. * [Build a Slackbot](https://developers.cloudflare.com/workers/tutorials/build-a-slackbot/) * [Create SMS notifications for your GitHub repository using Twilio](https://developers.cloudflare.com/workers/tutorials/github-sms-notifications-using-twilio/) * [Build a QR code generator](https://developers.cloudflare.com/workers/tutorials/build-a-qr-code-generator/) ```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/tutorials/","name":"Tutorials"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/tutorials/deploy-a-realtime-chat-app/","name":"Deploy a real-time chat application"}}]} ``` --- --- title: Deploy an Express.js application on Cloudflare Workers description: Learn how to deploy an Express.js application on Cloudflare Workers. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) ### Tags [ TypeScript ](https://developers.cloudflare.com/search/?tags=TypeScript) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/tutorials/deploy-an-express-app.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Deploy an Express.js application on Cloudflare Workers **Last reviewed:** 6 months ago In this tutorial, you will learn how to deploy an [Express.js ↗](https://expressjs.com/) application on Cloudflare Workers using the [Cloudflare Workers platform](https://developers.cloudflare.com/workers/) and [D1 database](https://developers.cloudflare.com/d1/). You will build a Members Registry API with basic Create, Read, Update, and Delete (CRUD) operations. You will use D1 as the database for storing and retrieving member data. ## Before you start All of the tutorials assume you have already completed the [Get started guide](https://developers.cloudflare.com/workers/get-started/guide/), which gets you set up with a Cloudflare Workers account, [C3 ↗](https://github.com/cloudflare/workers-sdk/tree/main/packages/create-cloudflare), and [Wrangler](https://developers.cloudflare.com/workers/wrangler/install-and-update/). ## Quick start If you want to skip the steps and get started quickly, select **Deploy to Cloudflare** 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/express-on-workers) This creates a repository in your GitHub account and deploys the application to Cloudflare Workers. Use this option if you are familiar with Cloudflare Workers, and wish to skip the step-by-step guidance. You may wish to manually follow the steps if you are new to Cloudflare Workers. ## 1\. Create a new Cloudflare Workers project Use [C3 ↗](https://developers.cloudflare.com/learning-paths/workers/get-started/c3-and-wrangler/#c3), the command-line tool for Cloudflare's developer products, to create a new directory and initialize a new Worker project: npm yarn pnpm ``` npm create cloudflare@latest -- express-d1-app ``` ``` yarn create cloudflare express-d1-app ``` ``` pnpm create cloudflare@latest express-d1-app ``` For setup, select the following options: * For _What would you like to start with?_, choose `Hello World example`. * For _Which template would you like to use?_, choose `Worker only`. * For _Which language do you want to use?_, choose `TypeScript`. * For _Do you want to use git for version control?_, choose `Yes`. * For _Do you want to deploy your application?_, choose `No` (we will be making some changes before deploying). Change into your new project directory: ``` cd express-d1-app ``` ## 2\. Install Express and dependencies In this tutorial, you will use [Express.js ↗](https://expressjs.com/), a popular web framework for Node.js. To use Express in a Cloudflare Workers environment, install Express along with the necessary TypeScript types: npm yarn pnpm bun ``` npm i express @types/express ``` ``` yarn add express @types/express ``` ``` pnpm add express @types/express ``` ``` bun add express @types/express ``` Express.js on Cloudflare Workers requires the `nodejs_compat` [compatibility flag](https://developers.cloudflare.com/workers/configuration/compatibility-flags/). This flag enables Node.js APIs and allows Express to run on the Workers runtime. Add the following to your Wrangler configuration file: * [ wrangler.jsonc ](#tab-panel-7938) * [ wrangler.toml ](#tab-panel-7939) JSONC ``` { "compatibility_flags": [ "nodejs_compat" ] } ``` TOML ``` compatibility_flags = [ "nodejs_compat" ] ``` ## 3\. Create a D1 database You will now create a D1 database to store member information. Use the `wrangler d1 create` command to create a new database: ``` npx wrangler d1 create members-db ``` The command will create a new D1 database and ask you the following questions: * **Would you like Wrangler to add it on your behalf?**: Type `Y`. * **What binding name would you like to use?**: Type `DB` and press Enter. * **For local dev, do you want to connect to the remote resource instead of a local resource?**: Type `N`. ``` ⛅️ wrangler 4.44.0 ─────────────────── ✅ Successfully created DB 'members-db' in region WNAM Created your new D1 database. To access your new D1 Database in your Worker, add the following snippet to your configuration file: { "d1_databases": [ { "binding": "members_db", "database_name": "members-db", "database_id": "" } ] } ✔ Would you like Wrangler to add it on your behalf? … yes ✔ What binding name would you like to use? … DB ✔ For local dev, do you want to connect to the remote resource instead of a local resource? … no ``` Explain Code The binding will be added to your Wrangler configuration file. * [ wrangler.jsonc ](#tab-panel-7940) * [ wrangler.toml ](#tab-panel-7941) JSONC ``` { "d1_databases": [ { "binding": "DB", "database_name": "members-db", "database_id": "" } ] } ``` TOML ``` [[d1_databases]] binding = "DB" database_name = "members-db" database_id = "" ``` ## 4\. Create database schema Create a directory called `schemas` in your project root, and inside it, create a file called `schema.sql`: schemas/schema.sql ``` DROP TABLE IF EXISTS members; CREATE TABLE IF NOT EXISTS members ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, email TEXT NOT NULL UNIQUE, joined_date TEXT NOT NULL ); -- Insert sample data INSERT INTO members (name, email, joined_date) VALUES ('Alice Johnson', 'alice@example.com', '2024-01-15'), ('Bob Smith', 'bob@example.com', '2024-02-20'), ('Carol Williams', 'carol@example.com', '2024-03-10'); ``` Explain Code This schema creates a `members` table with an auto-incrementing ID, name, email, and join date fields. It also inserts three sample members. Execute the schema file against your D1 database: ``` npx wrangler d1 execute members-db --file=./schemas/schema.sql ``` The above command creates the table in your local development database. You will deploy the schema to production later. ## 5\. Initialize Express application Update your `src/index.ts` file to set up Express with TypeScript. Replace the file content with the following: src/index.ts ``` import { env } from "cloudflare:workers"; import { httpServerHandler } from "cloudflare:node"; import express from "express"; const app = express(); // Middleware to parse JSON bodies app.use(express.json()); // Health check endpoint app.get("/", (req, res) => { res.json({ message: "Express.js running on Cloudflare Workers!" }); }); app.listen(3000); export default httpServerHandler({ port: 3000 }); ``` Explain Code This code initializes Express and creates a basic health check endpoint. The key import `import { env } from "cloudflare:workers"` allows you to access [bindings](https://developers.cloudflare.com/workers/runtime-apis/bindings/) like your D1 database from anywhere in your code. The [httpServerHandler](https://developers.cloudflare.com/workers/runtime-apis/nodejs/http/#httpserverhandler) integrates Express with the Workers runtime, enabling your application to handle HTTP requests on Cloudflare's network. Next, execute the typegen command to generate type definitions for your Worker environment: ``` npm run cf-typegen ``` ## 6\. Implement read operations Add endpoints to retrieve members from the database. Update your `src/index.ts` file by adding the following routes after the health check endpoint: src/index.ts ``` // GET all members app.get('/api/members', async (req, res) => { try { const { results } = await env.DB.prepare('SELECT * FROM members ORDER BY joined_date DESC').all(); res.json({ success: true, members: results }); } catch (error) { res.status(500).json({ success: false, error: 'Failed to fetch members' }); } }); // GET a single member by ID app.get('/api/members/:id', async (req, res) => { try { const { id } = req.params; const { results } = await env.DB.prepare('SELECT * FROM members WHERE id = ?').bind(id).all(); if (results.length === 0) { return res.status(404).json({ success: false, error: 'Member not found' }); } res.json({ success: true, member: results[0] }); } catch (error) { res.status(500).json({ success: false, error: 'Failed to fetch member' }); } }); ``` Explain Code These routes use the D1 binding (`env.DB`) to prepare SQL statements and execute them. Since you imported `env` from `cloudflare:workers` at the top of the file, it is accessible throughout your application. The `prepare`, `bind`, and `all` methods on the D1 binding allow you to safely query the database. Refer to [D1 Workers Binding API](https://developers.cloudflare.com/d1/worker-api/) for all available methods. ## 7\. Implement create operation Add an endpoint to create new members. Add the following route to your `src/index.ts` file: src/index.ts ``` // POST - Create a new member app.post("/api/members", async (req, res) => { try { const { name, email } = req.body; // Validate input if (!name || !email) { return res.status(400).json({ success: false, error: "Name and email are required", }); } // Basic email validation (simplified for tutorial purposes) // For production, consider using a validation library or more comprehensive checks if (!email.includes("@") || !email.includes(".")) { return res.status(400).json({ success: false, error: "Invalid email format", }); } const joined_date = new Date().toISOString().split("T")[0]; const result = await env.DB.prepare( "INSERT INTO members (name, email, joined_date) VALUES (?, ?, ?)" ) .bind(name, email, joined_date) .run(); if (result.success) { res.status(201).json({ success: true, message: "Member created successfully", id: result.meta.last_row_id, }); } else { res .status(500) .json({ success: false, error: "Failed to create member" }); } } catch (error: any) { // Handle unique constraint violation if (error.message?.includes("UNIQUE constraint failed")) { return res.status(409).json({ success: false, error: "Email already exists", }); } res.status(500).json({ success: false, error: "Failed to create member" }); } }); ``` Explain Code This endpoint validates the input, checks the email format, and inserts a new member into the database. It also handles duplicate email addresses by checking for unique constraint violations. ## 8\. Implement update operation Add an endpoint to update existing members. Add the following route to your `src/index.ts` file: src/index.ts ``` app.put("/api/members/:id", async (req, res) => { try { const { id } = req.params; const { name, email } = req.body; // Validate input if (!name && !email) { return res.status(400).json({ success: false, error: "At least one field (name or email) is required", }); } // Basic email validation if provided (simplified for tutorial purposes) // For production, consider using a validation library or more comprehensive checks if (email && (!email.includes("@") || !email.includes("."))) { return res.status(400).json({ success: false, error: "Invalid email format", }); } // Build dynamic update query const updates: string[] = []; const values: any[] = []; if (name) { updates.push("name = ?"); values.push(name); } if (email) { updates.push("email = ?"); values.push(email); } values.push(id); const result = await env.DB.prepare( `UPDATE members SET ${updates.join(", ")} WHERE id = ?` ) .bind(...values) .run(); if (result.meta.changes === 0) { return res .status(404) .json({ success: false, error: "Member not found" }); } res.json({ success: true, message: "Member updated successfully" }); } catch (error: any) { if (error.message?.includes("UNIQUE constraint failed")) { return res.status(409).json({ success: false, error: "Email already exists", }); } res.status(500).json({ success: false, error: "Failed to update member" }); } }); ``` Explain Code This endpoint allows updating either the name, email, or both fields of an existing member. It builds a dynamic SQL query based on the provided fields. ## 9\. Implement delete operation Add an endpoint to delete members. Add the following route to your `src/index.ts` file: src/index.ts ``` // DELETE - Delete a member app.delete("/api/members/:id", async (req, res) => { try { const { id } = req.params; const result = await env.DB.prepare("DELETE FROM members WHERE id = ?") .bind(id) .run(); if (result.meta.changes === 0) { return res .status(404) .json({ success: false, error: "Member not found" }); } res.json({ success: true, message: "Member deleted successfully" }); } catch (error) { res.status(500).json({ success: false, error: "Failed to delete member" }); } }); ``` Explain Code This endpoint deletes a member by their ID and returns an error if the member does not exist. ## 10\. Test locally Start the development server to test your API locally: ``` npm run dev ``` The development server will start, and you can access your API at `http://localhost:8787`. Open a new terminal window and test the endpoints using `curl`: Get all members ``` curl http://localhost:8787/api/members ``` ``` { "success": true, "members": [ { "id": 1, "name": "Alice Johnson", "email": "alice@example.com", "joined_date": "2024-01-15" }, { "id": 2, "name": "Bob Smith", "email": "bob@example.com", "joined_date": "2024-02-20" }, { "id": 3, "name": "Carol Williams", "email": "carol@example.com", "joined_date": "2024-03-10" } ] } ``` Explain Code Test creating a new member: Create a member ``` curl -X POST http://localhost:8787/api/members \ -H "Content-Type: application/json" \ -d '{"name": "David Brown", "email": "david@example.com"}' ``` ``` { "success": true, "message": "Member created successfully", "id": 4 } ``` Test getting a single member: Get a member by ID ``` curl http://localhost:8787/api/members/1 ``` Test updating a member: Update a member ``` curl -X PUT http://localhost:8787/api/members/1 \ -H "Content-Type: application/json" \ -d '{"name": "Alice Cooper"}' ``` Test deleting a member: Delete a member ``` curl -X DELETE http://localhost:8787/api/members/4 ``` ## 11\. Deploy to Cloudflare Workers Before deploying to production, execute the schema file against your remote (production) database: ``` npx wrangler d1 execute members-db --remote --file=./schemas/schema.sql ``` Now deploy your application to the Cloudflare network: ``` npm run deploy ``` ``` ⛅️ wrangler 4.44.0 ─────────────────── Total Upload: 1743.64 KiB / gzip: 498.65 KiB Worker Startup Time: 48 ms Your Worker has access to the following bindings: Binding Resource env.DB (members-db) D1 Database Uploaded express-d1-app (2.99 sec) Deployed express-d1-app triggers (5.26 sec) https://.workers.dev Current Version ID: ``` Explain Code After successful deployment, Wrangler will output your Worker's URL. ## 12\. Test production deployment Test your deployed API using the provided URL. Replace `` with your actual Worker URL: Test production API ``` curl https:///api/members ``` You should see the same member data you created in the production database. Create a new member in production: Create a member in production ``` curl -X POST https:///api/members \ -H "Content-Type: application/json" \ -d '{"name": "Eva Martinez", "email": "eva@example.com"}' ``` Your Express.js application with D1 database is now running on Cloudflare Workers. ## Conclusion In this tutorial, you built a Members Registry API using Express.js and D1 database, then deployed it to Cloudflare Workers. You implemented full CRUD operations (Create, Read, Update, Delete) and learned how to: * Set up an Express.js application for Cloudflare Workers * Create and configure a D1 database with bindings * Implement database operations using D1's prepared statements * Test your API locally and in production ## Next steps * Learn more about [D1 database features](https://developers.cloudflare.com/d1/) * Explore [Workers routing and middleware](https://developers.cloudflare.com/workers/runtime-apis/) * Add authentication to your API using [Workers authentication](https://developers.cloudflare.com/workers/runtime-apis/handlers/) * Implement pagination for large datasets using [D1 query optimization](https://developers.cloudflare.com/d1/worker-api/) ```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/tutorials/","name":"Tutorials"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/tutorials/deploy-an-express-app/","name":"Deploy an Express.js application on Cloudflare Workers"}}]} ``` --- --- title: Generate YouTube thumbnails with Workers and Cloudflare Image Resizing description: This tutorial explains how to programmatically generate a custom YouTube thumbnail using Cloudflare Workers. You may want to customize the thumbnail's design, call-to-actions and images used to encourage more viewers to watch your video. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) ### Tags [ JavaScript ](https://developers.cloudflare.com/search/?tags=JavaScript)[ Rust ](https://developers.cloudflare.com/search/?tags=Rust) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/tutorials/generate-youtube-thumbnails-with-workers-and-images.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Generate YouTube thumbnails with Workers and Cloudflare Image Resizing **Last reviewed:** about 3 years ago In this tutorial, you will learn how to programmatically generate a custom YouTube thumbnail using Cloudflare Workers and Cloudflare Image Resizing. You may want to generate a custom YouTube thumbnail to customize the thumbnail's design, call-to-actions and images used to encourage more viewers to watch your video. This tutorial will help you understand how to work with [Images](https://developers.cloudflare.com/images/),[Image Resizing](https://developers.cloudflare.com/images/transform-images/) and [Cloudflare Workers](https://developers.cloudflare.com/workers/). ## Before you start All of the tutorials assume you have already completed the [Get started guide](https://developers.cloudflare.com/workers/get-started/guide/), which gets you set up with a Cloudflare Workers account, [C3 ↗](https://github.com/cloudflare/workers-sdk/tree/main/packages/create-cloudflare), and [Wrangler](https://developers.cloudflare.com/workers/wrangler/install-and-update/). To follow this tutorial, make sure you have Node, Cargo, and [Wrangler](https://developers.cloudflare.com/workers/wrangler/install-and-update/) installed on your machine. ## Learning goals In this tutorial, you will learn how to: * Upload Images to Cloudflare with the Cloudflare dashboard or API. * Set up a Worker project with Wrangler. * Manipulate images with image transformations in your Worker. ## Upload your image To generate a custom thumbnail image, you first need to upload a background image to Cloudflare Images. This will serve as the image you use for transformations to generate the thumbnails. Cloudflare Images allows you to store, resize, optimize and deliver images in a fast and secure manner. To get started, upload your images to the Cloudflare dashboard or use the Upload API. ### Upload with the dashboard To upload an image using the Cloudflare dashboard: 1. In the Cloudflare dashboard, go to the **Transformations** page. [ Go to **Transformations** ](https://dash.cloudflare.com/?to=/:account/images/transformations) 2. Use **Quick Upload** to either drag and drop an image or click to browse and choose a file from your local files. 3. After the image is uploaded, view it using the generated URL. ### Upload with the API To upload your image with the [Upload via URL](https://developers.cloudflare.com/images/upload-images/upload-url/) API, refer to the example below: Terminal window ``` curl --request POST \ --url https://api.cloudflare.com/client/v4/accounts//images/v1 \ --header 'Authorization: Bearer ' \ --form 'url=' \ --form 'metadata={"key":"value"}' \ --form 'requireSignedURLs=false' ``` * `ACCOUNT_ID`: The current user's account id which can be found in your account settings. * `API_TOKEN`: Needs to be generated to scoping Images permission. * `PATH_TO_IMAGE`: Indicates the URL for the image you want to upload. You will then receive a response similar to this: ``` { "result": { "id": "2cdc28f0-017a-49c4-9ed7-87056c83901", "filename": "image.jpeg", "metadata": { "key": "value" }, "uploaded": "2022-01-31T16:39:28.458Z", "requireSignedURLs": false, "variants": [ "https://imagedelivery.net/Vi7wi5KSItxGFsWRG2Us6Q/2cdc28f0-017a-49c4-9ed7-87056c83901/public", "https://imagedelivery.net/Vi7wi5KSItxGFsWRG2Us6Q/2cdc28f0-017a-49c4-9ed7-87056c83901/thumbnail" ] }, "success": true, "errors": [], "messages": [] } ``` Explain Code Now that you have uploaded your image, you will use it as the background image for your video's thumbnail. ## Create a Worker to transform text to image After uploading your image, create a Worker that will enable you to transform text to image. This image can be used as an overlay on the background image you uploaded. Use the [rustwasm-worker-template ↗](https://github.com/cloudflare/workers-sdk/tree/main/templates/worker-rust). You will need the following before you begin: * A recent version of [Rust ↗](https://rustup.rs/). * Access to the `cargo-generate` subcommand: Terminal window ``` cargo install cargo-generate ``` Create a new Worker project using the `worker-rust` template: Terminal window ``` cargo generate https://github.com/cloudflare/rustwasm-worker-template ``` You will now make a few changes to the files in your project directory. 1. In the `lib.rs` file, add the following code block: ``` use worker::*; mod utils; #[event(fetch)] pub async fn main(req: Request, env: Env, _ctx: worker::Context) -> Result { // Optionally, get more helpful error messages written to the console in the case of a panic. utils::set_panic_hook(); let router = Router::new(); router .get("/", |_, _| Response::ok("Hello from Workers!")) .run(req, env) .await } ``` Explain Code 1. Update the `Cargo.toml` file in your `worker-to-text` project directory to use [text-to-png ↗](https://github.com/RookAndPawn/text-to-png), a Rust package for rendering text to PNG. Add the package as a dependency by running: Terminal window ``` cargo add text-to-png@0.2.0 ``` 1. Import the `text_to_png` library into your `worker-to-text` project's `lib.rs` file. ``` use text_to_png::{TextPng, TextRenderer}; use worker::*; mod utils; #[event(fetch)] pub async fn main(req: Request, env: Env, _ctx: worker::Context) -> Result { // Optionally, get more helpful error messages written to the console in the case of a panic. utils::set_panic_hook(); let router = Router::new(); router .get("/", |_, _| Response::ok("Hello from Workers!")) .run(req, env) .await } ``` Explain Code 1. Update `lib.rs` to create a `handle-slash` function that will activate the image transformation based on the text passed to the URL as a query parameter. ``` use text_to_png::{TextPng, TextRenderer}; use worker::*; mod utils; #[event(fetch)] pub async fn main(req: Request, env: Env, _ctx: worker::Context) -> Result { // Optionally, get more helpful error messages written to the console in the case of a panic. utils::set_panic_hook(); let router = Router::new(); router .get("/", |_, _| Response::ok("Hello from Workers!")) .run(req, env) .await } async fn handle_slash(text: String) -> Result {} ``` Explain Code 1. In the `handle-slash` function, call the `TextRenderer` by assigning it to a renderer value, specifying that you want to use a custom font. Then, use the `render_text_to_png_data` method to transform the text into image format. In this example, the custom font (`Inter-Bold.ttf`) is located in an `/assets` folder at the root of the project which will be used for generating the thumbnail. You must update this portion of the code to point to your custom font file. ``` use text_to_png::{TextPng, TextRenderer}; use worker::*; mod utils; #[event(fetch)] pub async fn main(req: Request, env: Env, _ctx: worker::Context) -> Result { // Optionally, get more helpful error messages written to the console in the case of a panic. utils::set_panic_hook(); let router = Router::new(); router .get("/", |_, _| Response::ok("Hello from Workers!")) .run(req, env) .await } async fn handle_slash(text: String) -> Result { let renderer = TextRenderer::try_new_with_ttf_font_data(include_bytes!("../assets/Inter-Bold.ttf")) .expect("Example font is definitely loadable"); let text_png: TextPng = renderer.render_text_to_png_data(text.replace("+", " "), 60, "003682").unwrap(); } ``` Explain Code 1. Rewrite the `Router` function to call `handle_slash` when a query is passed in the URL, otherwise return the `"Hello Worker!"` as the response. ``` use text_to_png::{TextPng, TextRenderer}; use worker::*; mod utils; #[event(fetch)] pub async fn main(req: Request, env: Env, _ctx: worker::Context) -> Result { // Optionally, get more helpful error messages written to the console in the case of a panic. utils::set_panic_hook(); let router = Router::new(); router .get_async("/", |req, _| async move { if let Some(text) = req.url()?.query() { handle_slash(text.into()).await } else { handle_slash("Hello Worker!".into()).await } }) .run(req, env) .await } async fn handle_slash(text: String) -> Result { let renderer = TextRenderer::try_new_with_ttf_font_data(include_bytes!("../assets/Inter-Bold.ttf")) .expect("Example font is definitely loadable"); let text_png: TextPng = renderer.render_text_to_png_data(text.replace("+", " "), 60, "003682").unwrap(); } ``` Explain Code 1. In your `lib.rs` file, set the headers to `content-type: image/png` so that the response is correctly rendered as a PNG image. ``` use text_to_png::{TextPng, TextRenderer}; use worker::*; mod utils; #[event(fetch)] pub async fn main(req: Request, env: Env, _ctx: worker::Context) -> Result { // Optionally, get more helpful error messages written to the console in the case of a panic. utils::set_panic_hook(); let router = Router::new(); router .get_async("/", |req, _| async move { if let Some(text) = req.url()?.query() { handle_slash(text.into()).await } else { handle_slash("Hello Worker!".into()).await } }) .run(req, env) .await } async fn handle_slash(text: String) -> Result { let renderer = TextRenderer::try_new_with_ttf_font_data(include_bytes!("../assets/Inter-Bold.ttf")) .expect("Example font is definitely loadable"); let text_png: TextPng = renderer.render_text_to_png_data(text.replace("+", " "), 60, "003682").unwrap(); let mut headers = Headers::new(); headers.set("content-type", "image/png")?; Ok(Response::from_bytes(text_png.data)?.with_headers(headers)) } ``` Explain Code The final `lib.rs` file should look as follows. Find the full code as an example repository on [GitHub ↗](https://github.com/cloudflare/workers-sdk/tree/main/templates/examples/worker-to-text). ``` use text_to_png::{TextPng, TextRenderer}; use worker::*; mod utils; #[event(fetch)] pub async fn main(req: Request, env: Env, _ctx: worker::Context) -> Result { // Optionally, get more helpful error messages written to the console in the case of a panic. utils::set_panic_hook(); let router = Router::new(); router .get_async("/", |req, _| async move { if let Some(text) = req.url()?.query() { handle_slash(text.into()).await } else { handle_slash("Hello Worker!".into()).await } }) .run(req, env) .await } async fn handle_slash(text: String) -> Result { let renderer = TextRenderer::try_new_with_ttf_font_data(include_bytes!("../assets/Inter-Bold.ttf")) .expect("Example font is definitely loadable"); let text = if text.len() > 128 { "Nope".into() } else { text }; let text = urlencoding::decode(&text).map_err(|_| worker::Error::BadEncoding)?; let text_png: TextPng = renderer.render_text_to_png_data(text.replace("+", " "), 60, "003682").unwrap(); let mut headers = Headers::new(); headers.set("content-type", "image/png")?; Ok(Response::from_bytes(text_png.data)?.with_headers(headers)) } ``` Explain Code After you have finished updating your project, start a local server for developing your Worker by running: Terminal window ``` npx wrangler dev ``` This should spin up a `localhost` instance with the image displayed: ![Run wrangler dev to start a local server for your Worker](https://developers.cloudflare.com/_astro/hello-worker.ot1qb0cF_Z2j0gbO.webp) Adding a query parameter with custom text, you should receive: ![Follow the instructions above to receive an output image](https://developers.cloudflare.com/_astro/build-serverles.BHasze4F_Zc150.webp) To deploy your Worker, open your Wrangler file and update the `name` key with your project's name. Below is an example with this tutorial's project name: * [ wrangler.jsonc ](#tab-panel-7942) * [ wrangler.toml ](#tab-panel-7943) JSONC ``` { "$schema": "./node_modules/wrangler/config-schema.json", "name": "worker-to-text" } ``` TOML ``` "$schema" = "./node_modules/wrangler/config-schema.json" name = "worker-to-text" ``` Then run the `npx wrangler deploy` command to deploy your Worker. Terminal window ``` npx wrangler deploy ``` A `.workers.dev` domain will be generated for your Worker after running `wrangler deploy`. You will use this domain in the main thumbnail image. ## Create a Worker to display the original image Create a Worker to serve the image you uploaded to Images by running: npm yarn pnpm ``` npm create cloudflare@latest -- thumbnail-image ``` ``` yarn create cloudflare thumbnail-image ``` ``` pnpm create cloudflare@latest thumbnail-image ``` For setup, select the following options: * For _What would you like to start with?_, choose `Hello World example`. * For _Which template would you like to use?_, choose `Worker only`. * For _Which language do you want to use?_, choose `JavaScript`. * For _Do you want to use git for version control?_, choose `Yes`. * For _Do you want to deploy your application?_, choose `No` (we will be making some changes before deploying). To start developing your Worker, `cd` into your new project directory: Terminal window ``` cd thumbnail-image ``` This will create a new Worker project named `thumbnail-image`. In the `src/index.js` file, add the following code block: JavaScript ``` export default { async fetch(request, env) { const url = new URL(request.url); if (url.pathname === "/original-image") { const image = await fetch( `https://imagedelivery.net/${env.CLOUDFLARE_ACCOUNT_HASH}/${IMAGE_ID}/public`, ); return image; } return new Response("Image Resizing with a Worker"); }, }; ``` Explain Code Update `env.CLOUDFLARE_ACCOUNT_HASH` with your [Cloudflare account ID](https://developers.cloudflare.com/fundamentals/account/find-account-and-zone-ids/). Update `env.IMAGE_ID` with your [image ID](https://developers.cloudflare.com/images/get-started/). Run your Worker and go to the `/original-image` route to review your image. ## Add custom text on your image You will now use [Cloudflare image transformations](https://developers.cloudflare.com/images/transform-images/), with the `fetch` method, to add your dynamic text image as an overlay on top of your background image. Start by displaying the resulting image on a different route. Call the new route `/thumbnail`. JavaScript ``` export default { async fetch(request, env) { const url = new URL(request.url); if (url.pathname === "/original-image") { const image = await fetch( `https://imagedelivery.net/${env.CLOUDFLARE_ACCOUNT_HASH}/${IMAGE_ID}/public`, ); return image; } if (url.pathname === "/thumbnail") { } return new Response("Image Resizing with a Worker"); }, }; ``` Explain Code Next, use the `fetch` method to apply the image transformation changes on top of the background image. The overlay options are nested in `options.cf.image`. JavaScript ``` export default { async fetch(request, env) { const url = new URL(request.url); if (url.pathname === "/original-image") { const image = await fetch( `https://imagedelivery.net/${env.CLOUDFLARE_ACCOUNT_HASH}/${IMAGE_ID}/public`, ); return image; } if (url.pathname === "/thumbnail") { fetch(imageURL, { cf: { image: {}, }, }); } return new Response("Image Resizing with a Worker"); }, }; ``` Explain Code The `imageURL` is the URL of the image you want to use as a background image. In the `cf.image` object, specify the options you want to apply to the background image. Note At time of publication, Cloudflare image transformations do not allow resizing images in a Worker that is stored in Cloudflare Images. Instead of using the image you served on the `/original-image` route, you will use the same image from a different source. Add your background image to an assets directory on GitHub and push your changes to GitHub. Copy the URL of the image upload by performing a left click on the image and selecting the **Copy Remote File Url** option. Replace the `imageURL` value with the copied remote URL. JavaScript ``` if (url.pathname === "/thumbnail") { const imageURL = "https://github.com/lauragift21/social-image-demo/blob/1ed9044463b891561b7438ecdecbdd9da48cdb03/assets/cover.png?raw=true"; fetch(imageURL, { cf: { image: {}, }, }); } ``` Next, add overlay options in the image object. Resize the image to the preferred width and height for YouTube thumbnails and use the [draw](https://developers.cloudflare.com/images/transform-images/draw-overlays/) option to add overlay text using the deployed URL of your `text-to-image` Worker. JavaScript ``` fetch(imageURL, { cf: { image: { width: 1280, height: 720, draw: [ { url: "https://text-to-image.examples.workers.dev", left: 40, }, ], }, }, }); ``` Explain Code Image transformations can only be tested when you deploy your Worker. To deploy your Worker, open your Wrangler file and update the `name` key with your project's name. Below is an example with this tutorial's project name: * [ wrangler.jsonc ](#tab-panel-7944) * [ wrangler.toml ](#tab-panel-7945) JSONC ``` { "$schema": "./node_modules/wrangler/config-schema.json", "name": "thumbnail-image" } ``` TOML ``` "$schema" = "./node_modules/wrangler/config-schema.json" name = "thumbnail-image" ``` Deploy your Worker by running: Terminal window ``` npx wrangler deploy ``` The command deploys your Worker to custom `workers.dev` subdomain. Go to your `.workers.dev` subdomain and go to the `/thumbnail` route. You should see the resized image with the text `Hello Workers!`. ![Follow the steps above to generate your resized image.](https://developers.cloudflare.com/_astro/thumbnail.z6EOGa1__kzK0u.webp) You will now make text applied dynamic. Making your text dynamic will allow you change the text and have it update on the image automatically. To add dynamic text, append any text attached to the `/thumbnail` URL using query parameters and pass it down to the `text-to-image` Worker URL as a parameter. JavaScript ``` for (const title of url.searchParams.values()) { try { const editedImage = await fetch(imageURL, { cf: { image: { width: 1280, height: 720, draw: [ { url: `https://text-to-image.examples.workers.dev/?${title}`, left: 50, }, ], }, }, }); return editedImage; } catch (error) { console.log(error); } } ``` Explain Code This will always return the text you pass as a query string in the generated image. This example URL, [https://socialcard.cdnuptime.com/thumbnail?Getting%20Started%20With%20Cloudflare%20Images ↗](https://socialcard.cdnuptime.com/thumbnail?Getting%20Started%20With%20Cloudflare%20Images), will generate the following image: ![An example thumbnail.](https://developers.cloudflare.com/_astro/thumbnail2.Bi3AcUzr_Z1qijsM.webp) By completing this tutorial, you have successfully made a custom YouTube thumbnail generator. ## Related resources In this tutorial, you learned how to use Cloudflare Workers and Cloudflare image transformations to generate custom YouTube thumbnails. To learn more about Cloudflare Workers and image transformations, refer to [Resize an image with a Worker](https://developers.cloudflare.com/images/transform-images/transform-via-workers/). ```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/tutorials/","name":"Tutorials"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/tutorials/generate-youtube-thumbnails-with-workers-and-images/","name":"Generate YouTube thumbnails with Workers and Cloudflare Image Resizing"}}]} ``` --- --- title: GitHub SMS notifications using Twilio description: This tutorial shows you how to build an SMS notification system on Workers to receive updates on a GitHub repository. Your Worker will send you a text update using Twilio when there is new activity on your repository. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) ### Tags [ JavaScript ](https://developers.cloudflare.com/search/?tags=JavaScript) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/tutorials/github-sms-notifications-using-twilio.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # GitHub SMS notifications using Twilio **Last reviewed:** over 2 years ago In this tutorial, you will learn to build an SMS notification system on Workers to receive updates on a GitHub repository. Your Worker will send you a text update using Twilio when there is new activity on your repository. You will learn how to: * Build webhooks using Workers. * Integrate Workers with GitHub and Twilio. * Use Worker secrets with Wrangler. ![Animated gif of receiving a text message on your phone after pushing changes to a repository](https://developers.cloudflare.com/images/workers/tutorials/github-sms/video-of-receiving-a-text-after-pushing-to-a-repo.gif) --- ## Before you start All of the tutorials assume you have already completed the [Get started guide](https://developers.cloudflare.com/workers/get-started/guide/), which gets you set up with a Cloudflare Workers account, [C3 ↗](https://github.com/cloudflare/workers-sdk/tree/main/packages/create-cloudflare), and [Wrangler](https://developers.cloudflare.com/workers/wrangler/install-and-update/). ## Create a Worker project Start by using `npm create cloudflare@latest` to create a Worker project in the command line: npm yarn pnpm ``` npm create cloudflare@latest -- github-twilio-notifications ``` ``` yarn create cloudflare github-twilio-notifications ``` ``` pnpm create cloudflare@latest github-twilio-notifications ``` For setup, select the following options: * For _What would you like to start with?_, choose `Hello World example`. * For _Which template would you like to use?_, choose `Worker only`. * For _Which language do you want to use?_, choose `JavaScript`. * For _Do you want to use git for version control?_, choose `Yes`. * For _Do you want to deploy your application?_, choose `No` (we will be making some changes before deploying). Make note of the URL that your application was deployed to. You will be using it when you configure your GitHub webhook. Terminal window ``` cd github-twilio-notifications ``` Inside of your new `github-sms-notifications` directory, `src/index.js` represents the entry point to your Cloudflare Workers application. You will configure this file for most of the tutorial. You will also need a GitHub account and a repository for this tutorial. If you do not have either setup, [create a new GitHub account ↗](https://github.com/join) and [create a new repository ↗](https://docs.github.com/en/get-started/quickstart/create-a-repo) to continue with this tutorial. First, create a webhook for your repository to post updates to your Worker. Inside of your Worker, you will then parse the updates. Finally, you will send a `POST` request to Twilio to send a text message to you. You can reference the finished code at this [GitHub repository ↗](https://github.com/rickyrobinett/workers-sdk/tree/main/templates/examples/github-sms-notifications-using-twilio). --- ## Configure GitHub To start, configure a GitHub webhook to post to your Worker when there is an update to the repository: 1. Go to your GitHub repository's **Settings** \> **Webhooks** \> **Add webhook**. 2. Set the Payload URL to the `/webhook` path on the Worker URL that you made note of when your application was first deployed. 3. In the **Content type** dropdown, select _application/json_. 4. In the **Secret** field, input a secret key of your choice. 5. In **Which events would you like to trigger this webhook?**, select **Let me select individual events**. Select the events you want to get notifications for (such as **Pull requests**, **Pushes**, and **Branch or tag creation**). 6. Select **Add webhook** to finish configuration. ![Following instructions to set up your webhook in the GitHub webhooks settings dashboard](https://developers.cloudflare.com/_astro/github-config-screenshot.BR7flpMR_Z1Yrdgb.webp) --- ## Parsing the response With your local environment set up, parse the repository update with your Worker. Initially, your generated `index.js` should look like this: JavaScript ``` export default { async fetch(request, env, ctx) { return new Response("Hello World!"); }, }; ``` Use the `request.method` property of [Request](https://developers.cloudflare.com/workers/runtime-apis/request/) to check if the request coming to your application is a `POST` request, and send an error response if the request is not a `POST` request. JavaScript ``` export default { async fetch(request, env, ctx) { if (request.method !== "POST") { return new Response("Please send a POST request!"); } }, }; ``` Next, validate that the request is sent with the right secret key. GitHub attaches a hash signature for [each payload using the secret key ↗](https://docs.github.com/en/developers/webhooks-and-events/webhooks/securing-your-webhooks). Use a helper function called `checkSignature` on the request to ensure the hash is correct. Then, you can access data from the webhook by parsing the request as JSON. JavaScript ``` async fetch(request, env, ctx) { if(request.method !== 'POST') { return new Response('Please send a POST request!'); } try { const rawBody = await request.text(); if (!checkSignature(rawBody, request.headers, env.GITHUB_SECRET_TOKEN)) { return new Response("Wrong password, try again", {status: 403}); } } catch (e) { return new Response(`Error: ${e}`); } }, ``` Explain Code The `checkSignature` function will use the Node.js crypto library to hash the received payload with your known secret key to ensure it matches the request hash. GitHub uses an HMAC hexdigest to compute the hash in the SHA-256 format. You will place this function at the top of your `index.js` file, before your export. JavaScript ``` import { createHmac, timingSafeEqual } from "node:crypto"; import { Buffer } from "node:buffer"; function checkSignature(text, headers, githubSecretToken) { const hmac = createHmac("sha256", githubSecretToken); hmac.update(text); const expectedSignature = hmac.digest("hex"); const actualSignature = headers.get("x-hub-signature-256"); const trusted = Buffer.from(`sha256=${expectedSignature}`, "ascii"); const untrusted = Buffer.from(actualSignature, "ascii"); return ( trusted.byteLength == untrusted.byteLength && timingSafeEqual(trusted, untrusted) ); } ``` Explain Code To make this work, you need to use [wrangler secret put](https://developers.cloudflare.com/workers/wrangler/commands/general/#secret-put) to set your `GITHUB_SECRET_TOKEN`. This token is the secret you picked earlier when configuring you GitHub webhook: Terminal window ``` npx wrangler secret put GITHUB_SECRET_TOKEN ``` Add the nodejs\_compat flag to your Wrangler file: * [ wrangler.jsonc ](#tab-panel-7946) * [ wrangler.toml ](#tab-panel-7947) JSONC ``` { "compatibility_flags": [ "nodejs_compat" ] } ``` TOML ``` compatibility_flags = [ "nodejs_compat" ] ``` --- ## Sending a text with Twilio You will send a text message to you about your repository activity using Twilio. You need a Twilio account and a phone number that can receive text messages. [Refer to the Twilio guide to get set up ↗](https://www.twilio.com/messaging/sms). (If you are new to Twilio, they have [an interactive game ↗](https://www.twilio.com/quest) where you can learn how to use their platform and get some free credits for beginners to the service.) You can then create a helper function to send text messages by sending a `POST` request to the Twilio API endpoint. [Refer to the Twilio reference ↗](https://www.twilio.com/docs/sms/api/message-resource#create-a-message-resource) to learn more about this endpoint. Create a new function called `sendText()` that will handle making the request to Twilio: JavaScript ``` async function sendText(accountSid, authToken, message) { const endpoint = `https://api.twilio.com/2010-04-01/Accounts/${accountSid}/Messages.json`; const encoded = new URLSearchParams({ To: "%YOUR_PHONE_NUMBER%", From: "%YOUR_TWILIO_NUMBER%", Body: message, }); const token = btoa(`${accountSid}:${authToken}`); const request = { body: encoded, method: "POST", headers: { Authorization: `Basic ${token}`, "Content-Type": "application/x-www-form-urlencoded", }, }; const response = await fetch(endpoint, request); const result = await response.json(); return Response.json(result); } ``` Explain Code To make this work, you need to set some secrets to hide your `ACCOUNT_SID` and `AUTH_TOKEN` from the source code. You can set secrets with [wrangler secret put](https://developers.cloudflare.com/workers/wrangler/commands/general/#secret-put) in your command line. Terminal window ``` npx wrangler secret put TWILIO_ACCOUNT_SID npx wrangler secret put TWILIO_AUTH_TOKEN ``` Modify your `githubWebhookHandler` to send a text message using the `sendText` function you just made. JavaScript ``` async fetch(request, env, ctx) { if(request.method !== 'POST') { return new Response('Please send a POST request!'); } try { const rawBody = await request.text(); if (!checkSignature(rawBody, request.headers, env.GITHUB_SECRET_TOKEN)) { return new Response('Wrong password, try again', {status: 403}); } const action = request.headers.get('X-GitHub-Event'); const json = JSON.parse(rawBody); const repoName = json.repository.full_name; const senderName = json.sender.login; return await sendText( env.TWILIO_ACCOUNT_SID, env.TWILIO_AUTH_TOKEN, `${senderName} completed ${action} onto your repo ${repoName}` ); } catch (e) { return new Response(`Error: ${e}`); } }; ``` Explain Code Run the `npx wrangler deploy` command to redeploy your Worker project: Terminal window ``` npx wrangler deploy ``` ![Video of receiving a text after pushing to a repo](https://developers.cloudflare.com/images/workers/tutorials/github-sms/video-of-receiving-a-text-after-pushing-to-a-repo.gif) Now when you make an update (that you configured in the GitHub **Webhook** settings) to your repository, you will get a text soon after. If you have never used Git before, refer to the [GIT Push and Pull Tutorial ↗](https://www.datacamp.com/tutorial/git-push-pull) for pushing to your repository. Reference the finished code [on GitHub ↗](https://github.com/rickyrobinett/workers-sdk/tree/main/templates/examples/github-sms-notifications-using-twilio). By completing this tutorial, you have learned how to build webhooks using Workers, integrate Workers with GitHub and Twilio, and use Worker secrets with Wrangler. ## Related resources * [Build a JAMStack app](https://developers.cloudflare.com/workers/tutorials/build-a-jamstack-app/) * [Build a QR code generator](https://developers.cloudflare.com/workers/tutorials/build-a-qr-code-generator/) ```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/tutorials/","name":"Tutorials"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/tutorials/github-sms-notifications-using-twilio/","name":"GitHub SMS notifications using Twilio"}}]} ``` --- --- title: Handle form submissions with Airtable description: Use Cloudflare Workers and Airtable to persist form submissions from a front-end user interface. Workers will handle incoming form submissions and use Airtables REST API to asynchronously persist the data in an Airtable base. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) ### Tags [ Forms ](https://developers.cloudflare.com/search/?tags=Forms)[ JavaScript ](https://developers.cloudflare.com/search/?tags=JavaScript) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/tutorials/handle-form-submissions-with-airtable.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Handle form submissions with Airtable **Last reviewed:** almost 3 years ago In this tutorial, you will use [Cloudflare Workers](https://developers.cloudflare.com/workers/) and [Airtable ↗](https://airtable.com) to persist form submissions from a front-end user interface. Airtable is a free-to-use spreadsheet solution that has an approachable API for developers. Workers will handle incoming form submissions and use Airtable's [REST API ↗](https://airtable.com/api) to asynchronously persist the data in an Airtable base (Airtable's term for a spreadsheet) for later reference. ![GIF of a complete Airtable and serverless function integration](https://developers.cloudflare.com/images/workers/tutorials/airtable/example.gif) ## Before you start All of the tutorials assume you have already completed the [Get started guide](https://developers.cloudflare.com/workers/get-started/guide/), which gets you set up with a Cloudflare Workers account, [C3 ↗](https://github.com/cloudflare/workers-sdk/tree/main/packages/create-cloudflare), and [Wrangler](https://developers.cloudflare.com/workers/wrangler/install-and-update/). ## 1\. Create a form For this tutorial, you will be building a Workers function that handles input from a contact form. The form this tutorial references will collect a first name, last name, email address, phone number, message subject, and a message. Build a form If this is your first time building a form and you would like to follow a tutorial to create a form with Cloudflare Pages, refer to the [HTML forms](https://developers.cloudflare.com/pages/tutorials/forms) tutorial. Review a simplified example of the form used in this tuttorial. Note that the `action` parameter of the `
` tag should point to the deployed Workers application that you will build in this tutorial. Your front-end code ```
``` Explain Code ## 2\. Create a Worker project To handle the form submission, create and deploy a Worker that parses the incoming form data and prepares it for submission to Airtable. Create a new `airtable-form-handler` Worker project: npm yarn pnpm ``` npm create cloudflare@latest -- airtable-form-handler ``` ``` yarn create cloudflare airtable-form-handler ``` ``` pnpm create cloudflare@latest airtable-form-handler ``` For setup, select the following options: * For _What would you like to start with?_, choose `Hello World example`. * For _Which template would you like to use?_, choose `Worker only`. * For _Which language do you want to use?_, choose `JavaScript`. * For _Do you want to use git for version control?_, choose `Yes`. * For _Do you want to deploy your application?_, choose `No` (we will be making some changes before deploying). Then, move into the newly created directory: Terminal window ``` cd airtable-form-handler ``` ## 3\. Configure an Airtable base When your Worker is complete, it will send data up to an Airtable base via Airtable's REST API. If you do not have an Airtable account, create one (the free plan is sufficient to complete this tutorial). In Airtable's dashboard, create a new base by selecting **Start from scratch**. After you have created a new base, set it up for use with the front-end form. Delete the existing columns, and create six columns, with the following field types: | Field name | Airtable field type | | ------------ | ------------------- | | First Name | "Single line text" | | Last Name | "Single line text" | | Email | "Email" | | Phone Number | "Phone number" | | Subject | "Single line text" | | Message | "Long text" | Note that the field names are case-sensitive. If you change the field names, you will need to exactly match your new field names in the API request you make to Airtable later in the tutorial. Finally, you can optionally rename your table -- by defaulte it will have a name like Table 1\. In the below code, we assume the table has been renamed with a more descriptive name, like `Form Submissions`. Next, navigate to [Airtable's API page ↗](https://airtable.com/api) and select your new base. Note that you must be logged into Airtable to see your base information. In the API documentation page, find your **Airtable base ID**. You will also need to create a **Personal access token** that you'll use to access your Airtable base. You can do so by visiting the [Personal access tokens ↗](https://airtable.com/create/tokens) page on Airtable's website and creating a new token. Make sure that you configure the token in the following way: * Scope: the `data.records:write` scope must be set on the token * Access: access should be granted to the base you have been working with in this tutorial The results access token should now be set in your application. To make the token available in your codebase, use the [wrangler secret](https://developers.cloudflare.com/workers/wrangler/commands/general/#secret) command. The `secret` command encrypts and stores environment variables for use in your function, without revealing them to users. Run `wrangler secret put`, passing `AIRTABLE_ACCESS_TOKEN` as the name of your secret: Terminal window ``` npx wrangler secret put AIRTABLE_ACCESS_TOKEN ``` ``` Enter the secret text you would like assigned to the variable AIRTABLE_ACCESS_TOKEN on the script named airtable-form-handler: ****** 🌀 Creating the secret for script name airtable-form-handler ✨ Success! Uploaded secret AIRTABLE_ACCESS_TOKEN. ``` Before you continue, review the keys that you should have from Airtable: 1. **Airtable Table Name**: The name for your table, like Form Submissions. 2. **Airtable Base ID**: The alphanumeric base ID found at the top of your base's API page. 3. **Airtable Access Token**: A Personal Access Token created by the user to access information about your new Airtable base. ## 4\. Submit data to Airtable With your Airtable base set up, and the keys and IDs you need to communicate with the API ready, you will now set up your Worker to persist data from your form into Airtable. In your Worker project's `index.js` file, replace the default code with a Workers fetch handler that can respond to requests. When the URL requested has a pathname of `/submit`, you will handle a new form submission, otherwise, you will return a `404 Not Found` response. JavaScript ``` export default { async fetch(request, env) { const url = new URL(request.url); if (url.pathname === "/submit") { await submitHandler(request, env); } return new Response("Not found", { status: 404 }); }, }; ``` The `submitHandler` has two functions. First, it will parse the form data coming from your HTML5 form. Once the data is parsed, use the Airtable API to persist a new row (a new form submission) to your table: JavaScript ``` async function submitHandler(request, env) { if (request.method !== "POST") { return new Response("Method Not Allowed", { status: 405, }); } const body = await request.formData(); const { first_name, last_name, email, phone, subject, message } = Object.fromEntries(body); // The keys in "fields" are case-sensitive, and // should exactly match the field names you set up // in your Airtable table, such as "First Name". const reqBody = { fields: { "First Name": first_name, "Last Name": last_name, Email: email, "Phone Number": phone, Subject: subject, Message: message, }, }; await createAirtableRecord(env, reqBody); } // Existing code // export default ... ``` Explain Code Prevent potential errors when accessing request.body The body of a [Request ↗](https://developer.mozilla.org/en-US/docs/Web/API/Request) can only be accessed once. If you previously used `request.formData()` in the same request, you may encounter a TypeError when attempting to access `request.body`. To avoid errors, create a clone of the Request object with `request.clone()` for each subsequent attempt to access a Request's body. Keep in mind that Workers have a [memory limit of 128 MB per Worker](https://developers.cloudflare.com/workers/platform/limits/#memory) and loading particularly large files into a Worker's memory multiple times may reach this limit. To ensure memory usage does not reach this limit, consider using [Streams](https://developers.cloudflare.com/workers/runtime-apis/streams/). While the majority of this function is concerned with parsing the request body (the data being sent as part of the request), there are two important things to note. First, if the HTTP method sent to this function is not `POST`, you will return a new response with the status code of [405 Method Not Allowed ↗](https://httpstatuses.com/405). The variable `reqBody` represents a collection of fields, which are key-value pairs for each column in your Airtable table. By formatting `reqBody` as an object with a collection of fields, you are creating a new record in your table with a value for each field. Then you call `createAirtableRecord` (the function you will define next). The `createAirtableRecord` function accepts a `body` parameter, which conforms to the Airtable API's required format — namely, a JavaScript object containing key-value pairs under `fields`, representing a single record to be created on your table: JavaScript ``` async function createAirtableRecord(env, body) { try { const result = fetch( `https://api.airtable.com/v0/${env.AIRTABLE_BASE_ID}/${encodeURIComponent(env.AIRTABLE_TABLE_NAME)}`, { method: "POST", body: JSON.stringify(body), headers: { Authorization: `Bearer ${env.AIRTABLE_ACCESS_TOKEN}`, "Content-Type": "application/json", }, }, ); return result; } catch (error) { console.error(error); } } // Existing code // async function submitHandler // export default ... ``` Explain Code To make an authenticated request to Airtable, you need to provide four constants that represent data about your Airtable account, base, and table name. You have already set `AIRTABLE_ACCESS_TOKEN` using `wrangler secret`, since it is a value that should be encrypted. The **Airtable base ID** and **table name**, and `FORM_URL` are values that can be publicly shared in places like GitHub. Use Wrangler's [vars](https://developers.cloudflare.com/workers/wrangler/migration/v1-to-v2/wrangler-legacy/configuration/#vars) feature to pass public environment variables from your Wrangler file. Add a `vars` table at the end of your Wrangler file: * [ wrangler.jsonc ](#tab-panel-7948) * [ wrangler.toml ](#tab-panel-7949) JSONC ``` { "$schema": "./node_modules/wrangler/config-schema.json", "name": "workers-airtable-form", "main": "src/index.js", // Set this to today's date "compatibility_date": "2026-04-14", "vars": { "AIRTABLE_BASE_ID": "exampleBaseId", "AIRTABLE_TABLE_NAME": "Form Submissions" } } ``` Explain Code TOML ``` "$schema" = "./node_modules/wrangler/config-schema.json" name = "workers-airtable-form" main = "src/index.js" # Set this to today's date compatibility_date = "2026-04-14" [vars] AIRTABLE_BASE_ID = "exampleBaseId" AIRTABLE_TABLE_NAME = "Form Submissions" ``` With all these fields submitted, it is time to deploy your Workers serverless function and get your form communicating with it. First, publish your Worker: Deploy your Worker ``` npx wrangler deploy ``` Your Worker project will deploy to a unique URL — for example, `https://workers-airtable-form.cloudflare.workers.dev`. This represents the first part of your front-end form's `action` attribute — the second part is the path for your form handler, which is `/submit`. In your front-end UI, configure your `form` tag as seen below: ```
``` After you have deployed your new form (refer to the [HTML forms](https://developers.cloudflare.com/pages/tutorials/forms) tutorial if you need help creating a form), you should be able to submit a new form submission and see the value show up immediately in Airtable: ![Example GIF of complete Airtable and serverless function integration](https://developers.cloudflare.com/images/workers/tutorials/airtable/example.gif) ## Conclusion With this tutorial completed, you have created a Worker that can accept form submissions and persist them to Airtable. You have learned how to parse form data, set up environment variables, and use the `fetch` API to make requests to external services outside of your Worker. ## Related resources * [Build a Slackbot](https://developers.cloudflare.com/workers/tutorials/build-a-slackbot) * [Build a To-Do List Jamstack App](https://developers.cloudflare.com/workers/tutorials/build-a-jamstack-app) * [Build a blog using Nuxt.js and Sanity.io on Cloudflare Pages](https://developers.cloudflare.com/pages/tutorials/build-a-blog-using-nuxt-and-sanity) * [James Quick's video on building a Cloudflare Workers + Airtable integration ↗](https://www.youtube.com/watch?v=tFQ2kbiu1K4) ```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/tutorials/","name":"Tutorials"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/tutorials/handle-form-submissions-with-airtable/","name":"Handle form submissions with Airtable"}}]} ``` --- --- title: Connect to a MySQL database with Cloudflare Workers description: This tutorial explains how to connect to a Cloudflare database using TCP Sockets and Hyperdrive. The Workers application you create in this tutorial will interact with a product database inside of MySQL. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) ### Tags [ MySQL ](https://developers.cloudflare.com/search/?tags=MySQL)[ TypeScript ](https://developers.cloudflare.com/search/?tags=TypeScript)[ SQL ](https://developers.cloudflare.com/search/?tags=SQL) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/tutorials/mysql.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Connect to a MySQL database with Cloudflare Workers **Last reviewed:** about 1 year ago In this tutorial, you will learn how to create a Cloudflare Workers application and connect it to a MySQL database using [TCP Sockets](https://developers.cloudflare.com/workers/runtime-apis/tcp-sockets/) and [Hyperdrive](https://developers.cloudflare.com/hyperdrive/). The Workers application you create in this tutorial will interact with a product database inside of MySQL. Note We recommend using [Hyperdrive](https://developers.cloudflare.com/hyperdrive/) to connect to your MySQL database. Hyperdrive provides optimal performance and will ensure secure connectivity between your Worker and your MySQL database. When connecting directly to your MySQL database (without Hyperdrive), the MySQL drivers rely on unsupported Node.js APIs to create secure connections, which prevents connections. ## Prerequisites To continue: 1. Sign up for a [Cloudflare account ↗](https://dash.cloudflare.com/sign-up/workers-and-pages) if you have not already. 2. Install [npm ↗](https://docs.npmjs.com/getting-started). 3. Install [Node.js ↗](https://nodejs.org/en/). Use a Node version manager like [Volta ↗](https://volta.sh/) or [nvm ↗](https://github.com/nvm-sh/nvm) to avoid permission issues and change Node.js versions. [Wrangler](https://developers.cloudflare.com/workers/wrangler/install-and-update/) requires a Node version of `16.17.0` or later. 4. Make sure you have access to a MySQL database. ## 1\. Create a Worker application First, use the [create-cloudflare CLI ↗](https://github.com/cloudflare/workers-sdk/tree/main/packages/create-cloudflare) to create a new Worker application. To do this, open a terminal window and run the following command: npm yarn pnpm ``` npm create cloudflare@latest -- mysql-tutorial ``` ``` yarn create cloudflare mysql-tutorial ``` ``` pnpm create cloudflare@latest mysql-tutorial ``` This will prompt you to install the [create-cloudflare ↗](https://www.npmjs.com/package/create-cloudflare) package and lead you through a setup wizard. For setup, select the following options: * For _What would you like to start with?_, choose `Hello World example`. * For _Which template would you like to use?_, choose `Worker only`. * For _Which language do you want to use?_, choose `TypeScript`. * For _Do you want to use git for version control?_, choose `Yes`. * For _Do you want to deploy your application?_, choose `No` (we will be making some changes before deploying). If you choose to deploy, you will be asked to authenticate (if not logged in already), and your project will be deployed. If you deploy, you can still modify your Worker code and deploy again at the end of this tutorial. Now, move into the newly created directory: Terminal window ``` cd mysql-tutorial ``` ## 2\. Enable Node.js compatibility [Node.js compatibility](https://developers.cloudflare.com/workers/runtime-apis/nodejs/) is required for database drivers, including mysql2, and needs to be configured for your Workers project. To enable both built-in runtime APIs and polyfills for your Worker or Pages project, add the [nodejs\_compat](https://developers.cloudflare.com/workers/configuration/compatibility-flags/#nodejs-compatibility-flag) [compatibility flag](https://developers.cloudflare.com/workers/configuration/compatibility-flags/#nodejs-compatibility-flag) to your [Wrangler configuration file](https://developers.cloudflare.com/workers/wrangler/configuration/), and set your compatibility date to September 23rd, 2024 or later. This will enable [Node.js compatibility](https://developers.cloudflare.com/workers/runtime-apis/nodejs/) for your Workers project. * [ wrangler.jsonc ](#tab-panel-7952) * [ wrangler.toml ](#tab-panel-7953) JSONC ``` { "compatibility_flags": [ "nodejs_compat" ], // Set this to today's date "compatibility_date": "2026-04-14" } ``` TOML ``` compatibility_flags = [ "nodejs_compat" ] # Set this to today's date compatibility_date = "2026-04-14" ``` ## 3\. Create a Hyperdrive configuration Create a Hyperdrive configuration using the connection string for your MySQL database. Terminal window ``` npx wrangler hyperdrive create --connection-string="mysql://user:password@HOSTNAME_OR_IP_ADDRESS:PORT/database_name" ``` This command outputs the Hyperdrive configuration `id` that will be used for your Hyperdrive [binding](https://developers.cloudflare.com/workers/runtime-apis/bindings/). Set up your binding by specifying the `id` in the Wrangler file. * [ wrangler.jsonc ](#tab-panel-7950) * [ wrangler.toml ](#tab-panel-7951) JSONC ``` { "$schema": "./node_modules/wrangler/config-schema.json", "name": "hyperdrive-example", "main": "src/index.ts", // Set this to today's date "compatibility_date": "2026-04-14", "compatibility_flags": [ "nodejs_compat" ], // Pasted from the output of `wrangler hyperdrive create --connection-string=[...]` above. "hyperdrive": [ { "binding": "HYPERDRIVE", "id": "" } ] } ``` Explain Code TOML ``` "$schema" = "./node_modules/wrangler/config-schema.json" name = "hyperdrive-example" main = "src/index.ts" # Set this to today's date compatibility_date = "2026-04-14" compatibility_flags = [ "nodejs_compat" ] [[hyperdrive]] binding = "HYPERDRIVE" id = "" ``` Explain Code ## 4\. Query your database from your Worker Install the [mysql2 ↗](https://github.com/sidorares/node-mysql2) driver: npm yarn pnpm bun ``` npm i mysql2@>3.13.0 ``` ``` yarn add mysql2@>3.13.0 ``` ``` pnpm add mysql2@>3.13.0 ``` ``` bun add mysql2@>3.13.0 ``` Note `mysql2` v3.13.0 or later is required Add the required Node.js compatibility flags and Hyperdrive binding to your `wrangler.jsonc` file: * [ wrangler.jsonc ](#tab-panel-7954) * [ wrangler.toml ](#tab-panel-7955) JSONC ``` { // required for database drivers to function "compatibility_flags": [ "nodejs_compat" ], // Set this to today's date "compatibility_date": "2026-04-14", "hyperdrive": [ { "binding": "HYPERDRIVE", "id": "" } ] } ``` Explain Code TOML ``` compatibility_flags = [ "nodejs_compat" ] # Set this to today's date compatibility_date = "2026-04-14" [[hyperdrive]] binding = "HYPERDRIVE" id = "" ``` Create a new `connection` instance and pass the Hyperdrive parameters: TypeScript ``` // mysql2 v3.13.0 or later is required import { createConnection } from "mysql2/promise"; export default { async fetch(request, env, ctx): Promise { // Create a new connection on each request. Hyperdrive maintains the underlying // database connection pool, so creating a new connection is fast. const connection = await createConnection({ host: env.HYPERDRIVE.host, user: env.HYPERDRIVE.user, password: env.HYPERDRIVE.password, database: env.HYPERDRIVE.database, port: env.HYPERDRIVE.port, // Required to enable mysql2 compatibility for Workers disableEval: true, }); try { // Sample query const [results, fields] = await connection.query("SHOW tables;"); // Return result rows as JSON return Response.json({ results, fields }); } catch (e) { console.error(e); return Response.json( { error: e instanceof Error ? e.message : e }, { status: 500 }, ); } }, } satisfies ExportedHandler; ``` Explain Code Note The minimum version of `mysql2` required for Hyperdrive is `3.13.0`. ## 5\. Deploy your Worker Run the following command to deploy your Worker: Terminal window ``` npx wrangler deploy ``` Your application is now live and accessible at `..workers.dev`. ## Next steps To build more with databases and Workers, refer to [Tutorials](https://developers.cloudflare.com/workers/tutorials) and explore the [Databases documentation](https://developers.cloudflare.com/workers/databases). If you have any questions, need assistance, or would like to share your project, join the Cloudflare Developer community on [Discord ↗](https://discord.cloudflare.com) to connect with fellow developers and the Cloudflare team. ```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/tutorials/","name":"Tutorials"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/tutorials/mysql/","name":"Connect to a MySQL database with Cloudflare Workers"}}]} ``` --- --- title: OpenAI GPT function calling with JavaScript and Cloudflare Workers description: Build a project that leverages OpenAI's function calling feature, available in OpenAI's latest Chat Completions API models. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) ### Tags [ AI ](https://developers.cloudflare.com/search/?tags=AI)[ JavaScript ](https://developers.cloudflare.com/search/?tags=JavaScript) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/tutorials/openai-function-calls-workers.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # OpenAI GPT function calling with JavaScript and Cloudflare Workers **Last reviewed:** almost 3 years ago In this tutorial, you will build a project that leverages [OpenAI's function calling ↗](https://platform.openai.com/docs/guides/function-calling) feature, available in OpenAI's latest Chat Completions API models. The function calling feature allows the AI model to intelligently decide when to call a function based on the input, and respond in JSON format to match the function's signature. You will use the function calling feature to request for the model to determine a website URL which contains information relevant to a message from the user, retrieve the text content of the site, and, finally, return a final response from the model informed by real-time web data. ## What you will learn * How to use OpenAI's function calling feature. * Integrating OpenAI's API in a Cloudflare Worker. * Fetching and processing website content using Cheerio. * Handling API responses and function calls in JavaScript. * Storing API keys as secrets with Wrangler. --- ## Before you start All of the tutorials assume you have already completed the [Get started guide](https://developers.cloudflare.com/workers/get-started/guide/), which gets you set up with a Cloudflare Workers account, [C3 ↗](https://github.com/cloudflare/workers-sdk/tree/main/packages/create-cloudflare), and [Wrangler](https://developers.cloudflare.com/workers/wrangler/install-and-update/). ## 1\. Create a new Worker project Create a Worker project in the command line: npm yarn pnpm ``` npm create cloudflare@latest -- openai-function-calling-workers ``` ``` yarn create cloudflare openai-function-calling-workers ``` ``` pnpm create cloudflare@latest openai-function-calling-workers ``` For setup, select the following options: * For _What would you like to start with?_, choose `Hello World example`. * For _Which template would you like to use?_, choose `Worker only`. * For _Which language do you want to use?_, choose `JavaScript`. * For _Do you want to use git for version control?_, choose `Yes`. * For _Do you want to deploy your application?_, choose `No` (we will be making some changes before deploying). Go to your new `openai-function-calling-workers` Worker project: Terminal window ``` cd openai-function-calling-workers ``` Inside of your new `openai-function-calling-workers` directory, find the `src/index.js` file. You will configure this file for most of the tutorial. You will also need an OpenAI account and API key for this tutorial. If you do not have one, [create a new OpenAI account ↗](https://platform.openai.com/signup) and [create an API key ↗](https://platform.openai.com/account/api-keys) to continue with this tutorial. Make sure to store you API key somewhere safe so you can use it later. ## 2\. Make a request to OpenAI With your Worker project created, make your first request to OpenAI. You will use the OpenAI node library to interact with the OpenAI API. In this project, you will also use the Cheerio library to handle processing the HTML content of websites npm yarn pnpm bun ``` npm i openai cheerio ``` ``` yarn add openai cheerio ``` ``` pnpm add openai cheerio ``` ``` bun add openai cheerio ``` Now, define the structure of your Worker in `index.js`: JavaScript ``` export default { async fetch(request, env, ctx) { // Initialize OpenAI API // Handle incoming requests return new Response("Hello World!"); }, }; ``` Above `export default`, add the imports for `openai` and `cheerio`: JavaScript ``` import OpenAI from "openai"; import * as cheerio from "cheerio"; ``` Within your `fetch` function, instantiate your `OpenAI` client: JavaScript ``` async fetch(request, env, ctx) { const openai = new OpenAI({ apiKey: env.OPENAI_API_KEY, }); // Handle incoming requests return new Response('Hello World!'); }, ``` Use [wrangler secret put](https://developers.cloudflare.com/workers/wrangler/commands/general/#secret-put) to set `OPENAI_API_KEY`. This [secret's](https://developers.cloudflare.com/workers/configuration/secrets/) value is the API key you created earlier in the OpenAI dashboard: Terminal window ``` npx wrangler secret put ``` For local development, create a new file `.dev.vars` in your Worker project and add this line. Make sure to replace `OPENAI_API_KEY` with your own OpenAI API key: ``` OPENAI_API_KEY = "" ``` Now, make a request to the OpenAI [Chat Completions API ↗](https://platform.openai.com/docs/guides/gpt/chat-completions-api): JavaScript ``` export default { async fetch(request, env, ctx) { const openai = new OpenAI({ apiKey: env.OPENAI_API_KEY, }); const url = new URL(request.url); const message = url.searchParams.get("message"); const messages = [ { role: "user", content: message ? message : "What's in the news today?", }, ]; const tools = [ { type: "function", function: { name: "read_website_content", description: "Read the content on a given website", parameters: { type: "object", properties: { url: { type: "string", description: "The URL to the website to read", }, }, required: ["url"], }, }, }, ]; const chatCompletion = await openai.chat.completions.create({ model: "gpt-4o-mini", messages: messages, tools: tools, tool_choice: "auto", }); const assistantMessage = chatCompletion.choices[0].message; console.log(assistantMessage); //Later you will continue handling the assistant's response here return new Response(assistantMessage.content); }, }; ``` Explain Code Review the arguments you are passing to OpenAI: * **model**: This is the model you want OpenAI to use for your request. In this case, you are using `gpt-4o-mini`. * **messages**: This is an array containing all messages that are part of the conversation. Initially you provide a message from the user, and we later add the response from the model. The content of the user message is either the `message` query parameter from the request URL or the default "What's in the news today?". * **tools**: An array containing the actions available to the AI model. In this example you only have one tool, `read_website_content`, which reads the content on a given website. * **name**: The name of your function. In this case, it is `read_website_content`. * **description**: A short description that lets the model know the purpose of the function. This is optional but helps the model know when to select the tool. * **parameters**: A JSON Schema object which describes the function. In this case we request a response containing an object with the required property `url`. * **tool\_choice**: This argument is technically optional as `auto` is the default. This argument indicates that either a function call or a normal message response can be returned by OpenAI. ## 3\. Building your `read_website_content()` function You will now need to define the `read_website_content` function, which is referenced in the `tools` array. The `read_website_content` function fetches the content of a given URL and extracts the text from `

` tags using the `cheerio` library: Add this code above the `export default` block in your `index.js` file: JavaScript ``` async function read_website_content(url) { console.log("reading website content"); const response = await fetch(url); const body = await response.text(); let cheerioBody = cheerio.load(body); const resp = { website_body: cheerioBody("p").text(), url: url, }; return JSON.stringify(resp); } ``` Explain Code In this function, you take the URL that you received from OpenAI and use JavaScript's [Fetch API ↗](https://developer.mozilla.org/en-US/docs/Web/API/Fetch%5FAPI/Using%5FFetch) to pull the content of the website and extract the paragraph text. Now we need to determine when to call this function. ## 4\. Process the Assistant's Messages Next, we need to process the response from the OpenAI API to check if it includes any function calls. If a function call is present, you should execute the corresponding function in your Worker. Note that the assistant may request multiple function calls. Modify the fetch method within the `export default` block as follows: JavaScript ``` // ... your previous code ... if (assistantMessage.tool_calls) { for (const toolCall of assistantMessage.tool_calls) { if (toolCall.function.name === "read_website_content") { const url = JSON.parse(toolCall.function.arguments).url; const websiteContent = await read_website_content(url); messages.push({ role: "tool", tool_call_id: toolCall.id, name: toolCall.function.name, content: websiteContent, }); } } const secondChatCompletion = await openai.chat.completions.create({ model: "gpt-4o-mini", messages: messages, }); return new Response(secondChatCompletion.choices[0].message.content); } else { // this is your existing return statement return new Response(assistantMessage.content); } ``` Explain Code Check if the assistant message contains any function calls by checking for the `tool_calls` property. Because the AI model can call multiple functions by default, you need to loop through any potential function calls and add them to the `messages` array. Each `read_website_content` call will invoke the `read_website_content` function you defined earlier and pass the URL generated by OpenAI as an argument. \` The `secondChatCompletion` is needed to provide a response informed by the data you retrieved from each function call. Now, the last step is to deploy your Worker. Test your code by running `npx wrangler dev` and open the provided url in your browser. This will now show you OpenAI’s response using real-time information from the retrieved web data. ## 5\. Deploy your Worker application To deploy your application, run the `npx wrangler deploy` command to deploy your Worker application: Terminal window ``` npx wrangler deploy ``` You can now preview your Worker at `..workers.dev`. Going to this URL will display the response from OpenAI. Optionally, add the `message` URL parameter to write a custom message: for example, `https://..workers.dev/?message=What is the weather in NYC today?`. ## 6\. Next steps Reference the [finished code for this tutorial on GitHub ↗](https://github.com/LoganGrasby/Cloudflare-OpenAI-Functions-Demo/blob/main/src/worker.js). To continue working with Workers and AI, refer to [the guide on using LangChain and Cloudflare Workers together ↗](https://blog.cloudflare.com/langchain-and-cloudflare/) or [how to build a ChatGPT plugin with Cloudflare Workers ↗](https://blog.cloudflare.com/magic-in-minutes-how-to-build-a-chatgpt-plugin-with-cloudflare-workers/). If you have any questions, need assistance, or would like to share your project, join the Cloudflare Developer community on [Discord ↗](https://discord.cloudflare.com) to connect with fellow developers and the Cloudflare team. ```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/tutorials/","name":"Tutorials"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/tutorials/openai-function-calls-workers/","name":"OpenAI GPT function calling with JavaScript and Cloudflare Workers"}}]} ``` --- --- title: Connect to a PostgreSQL database with Cloudflare Workers description: This tutorial explains how to connect to a Postgres database with Cloudflare Workers. The Workers application you create in this tutorial will interact with a product database inside of Postgres. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) ### Tags [ Postgres ](https://developers.cloudflare.com/search/?tags=Postgres)[ TypeScript ](https://developers.cloudflare.com/search/?tags=TypeScript)[ SQL ](https://developers.cloudflare.com/search/?tags=SQL) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/tutorials/postgres.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Connect to a PostgreSQL database with Cloudflare Workers **Last reviewed:** 10 months ago In this tutorial, you will learn how to create a Cloudflare Workers application and connect it to a PostgreSQL database using [TCP Sockets](https://developers.cloudflare.com/workers/runtime-apis/tcp-sockets/) and [Hyperdrive](https://developers.cloudflare.com/hyperdrive/). The Workers application you create in this tutorial will interact with a product database inside of PostgreSQL. ## Prerequisites To continue: 1. Sign up for a [Cloudflare account ↗](https://dash.cloudflare.com/sign-up/workers-and-pages) if you have not already. 2. Install [npm ↗](https://docs.npmjs.com/getting-started). 3. Install [Node.js ↗](https://nodejs.org/en/). Use a Node version manager like [Volta ↗](https://volta.sh/) or [nvm ↗](https://github.com/nvm-sh/nvm) to avoid permission issues and change Node.js versions. [Wrangler](https://developers.cloudflare.com/workers/wrangler/install-and-update/) requires a Node version of `16.17.0` or later. 4. Make sure you have access to a PostgreSQL database. ## 1\. Create a Worker application First, use the [create-cloudflare CLI ↗](https://github.com/cloudflare/workers-sdk/tree/main/packages/create-cloudflare) to create a new Worker application. To do this, open a terminal window and run the following command: npm yarn pnpm ``` npm create cloudflare@latest -- postgres-tutorial ``` ``` yarn create cloudflare postgres-tutorial ``` ``` pnpm create cloudflare@latest postgres-tutorial ``` This will prompt you to install the [create-cloudflare ↗](https://www.npmjs.com/package/create-cloudflare) package and lead you through a setup wizard. For setup, select the following options: * For _What would you like to start with?_, choose `Hello World example`. * For _Which template would you like to use?_, choose `Worker only`. * For _Which language do you want to use?_, choose `TypeScript`. * For _Do you want to use git for version control?_, choose `Yes`. * For _Do you want to deploy your application?_, choose `No` (we will be making some changes before deploying). If you choose to deploy, you will be asked to authenticate (if not logged in already), and your project will be deployed. If you deploy, you can still modify your Worker code and deploy again at the end of this tutorial. Now, move into the newly created directory: Terminal window ``` cd postgres-tutorial ``` ### Enable Node.js compatibility [Node.js compatibility](https://developers.cloudflare.com/workers/runtime-apis/nodejs/) is required for database drivers, including Postgres.js, and needs to be configured for your Workers project. To enable both built-in runtime APIs and polyfills for your Worker or Pages project, add the [nodejs\_compat](https://developers.cloudflare.com/workers/configuration/compatibility-flags/#nodejs-compatibility-flag) [compatibility flag](https://developers.cloudflare.com/workers/configuration/compatibility-flags/#nodejs-compatibility-flag) to your [Wrangler configuration file](https://developers.cloudflare.com/workers/wrangler/configuration/), and set your compatibility date to September 23rd, 2024 or later. This will enable [Node.js compatibility](https://developers.cloudflare.com/workers/runtime-apis/nodejs/) for your Workers project. * [ wrangler.jsonc ](#tab-panel-7960) * [ wrangler.toml ](#tab-panel-7961) JSONC ``` { "compatibility_flags": [ "nodejs_compat" ], // Set this to today's date "compatibility_date": "2026-04-14" } ``` TOML ``` compatibility_flags = [ "nodejs_compat" ] # Set this to today's date compatibility_date = "2026-04-14" ``` ## 2\. Add the PostgreSQL connection library To connect to a PostgreSQL database, you will need the `pg` library. In your Worker application directory, run the following command to install the library: npm yarn pnpm bun ``` npm i pg ``` ``` yarn add pg ``` ``` pnpm add pg ``` ``` bun add pg ``` Next, install the TypeScript types for the `pg` library to enable type checking and autocompletion in your TypeScript code: npm yarn pnpm bun ``` npm i -D @types/pg ``` ``` yarn add -D @types/pg ``` ``` pnpm add -D @types/pg ``` ``` bun add -d @types/pg ``` Note Make sure you are using `pg` (`node-postgres`) version `8.16.3` or higher. ## 3\. Configure the connection to the PostgreSQL database Choose one of the two methods to connect to your PostgreSQL database: 1. [Use a connection string](#use-a-connection-string). 2. [Set explicit parameters](#set-explicit-parameters). ### Use a connection string A connection string contains all the information needed to connect to a database. It is a URL that contains the following information: ``` postgresql://username:password@host:port/database ``` Replace `username`, `password`, `host`, `port`, and `database` with the appropriate values for your PostgreSQL database. Set your connection string as a [secret](https://developers.cloudflare.com/workers/configuration/secrets/) so that it is not stored as plain text. Use [wrangler secret put](https://developers.cloudflare.com/workers/wrangler/commands/general/#secret) with the example variable name `DB_URL`: Terminal window ``` npx wrangler secret put DB_URL ``` ``` ➜ wrangler secret put DB_URL ------------------------------------------------------- ? Enter a secret value: › ******************** ✨ Success! Uploaded secret DB_URL ``` Set your `DB_URL` secret locally in a `.dev.vars` file as documented in [Local Development with Secrets](https://developers.cloudflare.com/workers/configuration/secrets/). .dev.vars ``` DB_URL="" ``` ### Set explicit parameters Configure each database parameter as an [environment variable](https://developers.cloudflare.com/workers/configuration/environment-variables/) via the [Cloudflare dashboard](https://developers.cloudflare.com/workers/configuration/environment-variables/#add-environment-variables-via-the-dashboard) or in your Wrangler file. Refer to an example of a Wrangler file configuration: * [ wrangler.jsonc ](#tab-panel-7956) * [ wrangler.toml ](#tab-panel-7957) JSONC ``` { "vars": { "DB_USERNAME": "postgres", // Set your password by creating a secret so it is not stored as plain text "DB_HOST": "ep-aged-sound-175961.us-east-2.aws.neon.tech", "DB_PORT": 5432, "DB_NAME": "productsdb" } } ``` TOML ``` [vars] DB_USERNAME = "postgres" DB_HOST = "ep-aged-sound-175961.us-east-2.aws.neon.tech" DB_PORT = 5_432 DB_NAME = "productsdb" ``` To set your password as a [secret](https://developers.cloudflare.com/workers/configuration/secrets/) so that it is not stored as plain text, use [wrangler secret put](https://developers.cloudflare.com/workers/wrangler/commands/general/#secret). `DB_PASSWORD` is an example variable name for this secret to be accessed in your Worker: Terminal window ``` npx wrangler secret put DB_PASSWORD ``` ``` ------------------------------------------------------- ? Enter a secret value: › ******************** ✨ Success! Uploaded secret DB_PASSWORD ``` ## 4\. Connect to the PostgreSQL database in the Worker Open your Worker's main file (for example, `worker.ts`) and import the `Client` class from the `pg` library: TypeScript ``` import { Client } from "pg"; ``` In the `fetch` event handler, connect to the PostgreSQL database using your chosen method, either the connection string or the explicit parameters. ### Use a connection string TypeScript ``` // create a new Client instance using the connection string const sql = new Client({ connectionString: env.DB_URL }); // connect to the PostgreSQL database await sql.connect(); ``` ### Set explicit parameters TypeScript ``` // create a new Client instance using explicit parameters const sql = new Client({ username: env.DB_USERNAME, password: env.DB_PASSWORD, host: env.DB_HOST, port: env.DB_PORT, database: env.DB_NAME, ssl: true, // Enable SSL for secure connections }); // connect to the PostgreSQL database await sql.connect(); ``` Explain Code ## 5\. Interact with the products database To demonstrate how to interact with the products database, you will fetch data from the `products` table by querying the table when a request is received. Note If you are following along in your own PostgreSQL instance, set up the `products` using the following SQL `CREATE TABLE` statement. This statement defines the columns and their respective data types for the `products` table: ``` CREATE TABLE products ( id SERIAL PRIMARY KEY, name VARCHAR(255) NOT NULL, description TEXT, price DECIMAL(10, 2) NOT NULL ); ``` Replace the existing code in your `worker.ts` file with the following code: TypeScript ``` import { Client } from "pg"; export default { async fetch(request, env, ctx): Promise { // Create a new Client instance using the connection string // or explicit parameters as shown in the previous steps. // Here, we are using the connection string method. const sql = new Client({ connectionString: env.DB_URL, }); // Connect to the PostgreSQL database await sql.connect(); // Query the products table const result = await sql.query("SELECT * FROM products"); // Return the result as JSON return new Response(JSON.stringify(result.rows), { headers: { "Content-Type": "application/json", }, }); }, } satisfies ExportedHandler; ``` Explain Code This code establishes a connection to the PostgreSQL database within your Worker application and queries the `products` table, returning the results as a JSON response. ## 6\. Deploy your Worker Run the following command to deploy your Worker: Terminal window ``` npx wrangler deploy ``` Your application is now live and accessible at `..workers.dev`. After deploying, you can interact with your PostgreSQL products database using your Cloudflare Worker. Whenever a request is made to your Worker's URL, it will fetch data from the `products` table and return it as a JSON response. You can modify the query as needed to retrieve the desired data from your products database. ## 7\. Insert a new row into the products database To insert a new row into the `products` table, create a new API endpoint in your Worker that handles a `POST` request. When a `POST` request is received with a JSON payload, the Worker will insert a new row into the `products` table with the provided data. Assume the `products` table has the following columns: `id`, `name`, `description`, and `price`. Add the following code snippet inside the `fetch` event handler in your `worker.ts` file, before the existing query code: TypeScript ``` import { Client } from "pg"; export default { async fetch(request, env, ctx): Promise { // Create a new Client instance using the connection string // or explicit parameters as shown in the previous steps. // Here, we are using the connection string method. const sql = new Client({ connectionString: env.DB_URL, }); // Connect to the PostgreSQL database await sql.connect(); const url = new URL(request.url); if (request.method === "POST" && url.pathname === "/products") { // Parse the request's JSON payload const productData = (await request.json()) as { name: string; description: string; price: number; }; const name = productData.name, description = productData.description, price = productData.price; // Insert the new product into the products table const insertResult = await sql.query( `INSERT INTO products(name, description, price) VALUES($1, $2, $3) RETURNING *`, [name, description, price], ); // Return the inserted row as JSON return new Response(JSON.stringify(insertResult.rows), { headers: { "Content-Type": "application/json" }, }); } // Query the products table const result = await sql.query("SELECT * FROM products"); // Return the result as JSON return new Response(JSON.stringify(result.rows), { headers: { "Content-Type": "application/json", }, }); }, } satisfies ExportedHandler; ``` Explain Code This code snippet does the following: 1. Checks if the request is a `POST` request and the URL path is `/products`. 2. Parses the JSON payload from the request. 3. Constructs an `INSERT` SQL query using the provided product data. 4. Executes the query, inserting the new row into the `products` table. 5. Returns the inserted row as a JSON response. Now, when you send a `POST` request to your Worker's URL with the `/products` path and a JSON payload, the Worker will insert a new row into the `products` table with the provided data. When a request to `/` is made, the Worker will return all products in the database. After making these changes, deploy the Worker again by running: Terminal window ``` npx wrangler deploy ``` You can now use your Cloudflare Worker to insert new rows into the `products` table. To test this functionality, send a `POST` request to your Worker's URL with the `/products` path, along with a JSON payload containing the new product data: ``` { "name": "Sample Product", "description": "This is a sample product", "price": 19.99 } ``` You have successfully created a Cloudflare Worker that connects to a PostgreSQL database and handles fetching data and inserting new rows into a products table. ## 8\. Use Hyperdrive to accelerate queries Create a Hyperdrive configuration using the connection string for your PostgreSQL database. Terminal window ``` npx wrangler hyperdrive create --connection-string="postgres://user:password@HOSTNAME_OR_IP_ADDRESS:PORT/database_name" --caching-disabled ``` This command outputs the Hyperdrive configuration `id` that will be used for your Hyperdrive [binding](https://developers.cloudflare.com/workers/runtime-apis/bindings/). Set up your binding by specifying the `id` in the Wrangler file. * [ wrangler.jsonc ](#tab-panel-7958) * [ wrangler.toml ](#tab-panel-7959) JSONC ``` { "$schema": "./node_modules/wrangler/config-schema.json", "name": "hyperdrive-example", "main": "src/index.ts", // Set this to today's date "compatibility_date": "2026-04-14", "compatibility_flags": [ "nodejs_compat" ], // Pasted from the output of `wrangler hyperdrive create --connection-string=[...]` above. "hyperdrive": [ { "binding": "HYPERDRIVE", "id": "" } ] } ``` Explain Code TOML ``` "$schema" = "./node_modules/wrangler/config-schema.json" name = "hyperdrive-example" main = "src/index.ts" # Set this to today's date compatibility_date = "2026-04-14" compatibility_flags = [ "nodejs_compat" ] [[hyperdrive]] binding = "HYPERDRIVE" id = "" ``` Explain Code Create the types for your Hyperdrive binding using the following command: Terminal window ``` npx wrangler types ``` Replace your existing connection string in your Worker code with the Hyperdrive connection string. JavaScript ``` export default { async fetch(request, env, ctx): Promise { const sql = new Client({connectionString: env.HYPERDRIVE.connectionString}) const url = new URL(request.url); //rest of the routes and database queries }, } satisfies ExportedHandler; ``` ## 9\. Redeploy your Worker Run the following command to deploy your Worker: Terminal window ``` npx wrangler deploy ``` Your Worker application is now live and accessible at `..workers.dev`, using Hyperdrive. Hyperdrive accelerates database queries by pooling your connections and caching your requests across the globe. ## Next steps To build more with databases and Workers, refer to [Tutorials](https://developers.cloudflare.com/workers/tutorials) and explore the [Databases documentation](https://developers.cloudflare.com/workers/databases). If you have any questions, need assistance, or would like to share your project, join the Cloudflare Developer community on [Discord ↗](https://discord.cloudflare.com) to connect with fellow developers and the Cloudflare team. ```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/tutorials/","name":"Tutorials"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/tutorials/postgres/","name":"Connect to a PostgreSQL database with Cloudflare Workers"}}]} ``` --- --- title: Send Emails With Postmark description: This tutorial explains how to send transactional emails from Workers using Postmark. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) ### Tags [ JavaScript ](https://developers.cloudflare.com/search/?tags=JavaScript) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/tutorials/send-emails-with-postmark.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Send Emails With Postmark **Last reviewed:** almost 2 years ago In this tutorial, you will learn how to send transactional emails from Workers using [Postmark ↗](https://postmarkapp.com/). At the end of this tutorial, you’ll be able to: * Create a Worker to send emails. * Sign up and add a Cloudflare domain to Postmark. * Send emails from your Worker using Postmark. * Store API keys securely with secrets. ## Prerequisites To continue with this tutorial, you’ll need: * A [Cloudflare account ↗](https://dash.cloudflare.com/sign-up/workers-and-pages), if you don’t already have one. * A [registered](https://developers.cloudflare.com/registrar/get-started/register-domain/) domain. * Installed [npm ↗](https://docs.npmjs.com/getting-started). * A [Postmark account ↗](https://account.postmarkapp.com/sign%5Fup). ## Create a Worker project Start by using [C3](https://developers.cloudflare.com/pages/get-started/c3/) to create a Worker project in the command line, then, answer the prompts: Terminal window ``` npm create cloudflare@latest ``` Alternatively, you can use CLI arguments to speed things up: Terminal window ``` npm create cloudflare@latest email-with-postmark -- --type=hello-world --ts=false --git=true --deploy=false ``` This creates a simple hello-world Worker having the following content: JavaScript ``` export default { async fetch(request, env, ctx) { return new Response("Hello World!"); }, }; ``` ## Add your domain to Postmark If you don’t already have a Postmark account, you can sign up for a [free account here ↗](https://account.postmarkapp.com/sign%5Fup). After signing up, check your inbox for a link to confirm your sender signature. This verifies and enables you to send emails from your registered email address. To enable email sending from other addresses on your domain, navigate to `Sender Signatures` on the Postmark dashboard, `Add Domain or Signature` \> `Add Domain`, then type in your domain and click on `Verify Domain`. Next, you’re presented with a list of DNS records to add to your Cloudflare domain. On your Cloudflare dashboard, select the domain you entered earlier and navigate to `DNS` \> `Records`. Copy/paste the DNS records (DKIM, and Return-Path) from Postmark to your Cloudflare domain. ![Image of adding DNS records to a Cloudflare domain](https://developers.cloudflare.com/_astro/add_dns_records.CuwqhmEV_Z1PK0DA.webp) Note If you need more help adding DNS records in Cloudflare, refer to [Manage DNS records](https://developers.cloudflare.com/dns/manage-dns-records/how-to/create-dns-records/). When that’s done, head back to Postmark and click on the `Verify` buttons. If all records are properly configured, your domain status should be updated to `Verified`. ![Image of domain verification on the Postmark dashboard](https://developers.cloudflare.com/_astro/verified_domain.CSwUI8xQ_ZJiRKw.webp) To grab your API token, navigate to the `Servers` tab, then `My First Server` \> `API Tokens`, then copy your API key to a safe place. ## Send emails from your Worker The final step is putting it all together in a Worker. In your Worker, make a post request with `fetch` to Postmark’s email API and include your token and message body: Note [Postmark’s JavaScript library ↗](https://www.npmjs.com/package/postmark) is currently not supported on Workers. Use the [email API ↗](https://postmarkapp.com/developer/user-guide/send-email-with-api) instead. ``` export default { async fetch(request, env, ctx) { return await fetch("https://api.postmarkapp.com/email", { method: "POST", headers: { "Content-Type": "application/json", "X-Postmark-Server-Token": "your_postmark_api_token_here", }, body: JSON.stringify({ From: "hello@example.com", To: "someone@example.com", Subject: "Hello World", HtmlBody: "

Hello from Workers

", }), }); }, }; ``` Explain Code To test your code locally, run the following command and navigate to [http://localhost:8787/ ↗](http://localhost:8787/) in a browser: Terminal window ``` npm start ``` Deploy your Worker with `npm run deploy`. ## Move API token to Secrets Sensitive information such as API keys and token should always be stored in secrets. All secrets are encrypted to add an extra layer of protection. That said, it’s a good idea to move your API token to a secret and access it from the environment of your Worker. To add secrets for local development, create a `.dev.vars` file which works exactly like a `.env` file: ``` POSTMARK_API_TOKEN=your_postmark_api_token_here ``` Also ensure the secret is added to your deployed worker by running: Add secret to deployed Worker ``` npx wrangler secret put POSTMARK_API_TOKEN ``` The added secret can be accessed on via the `env` parameter passed to your Worker’s fetch event handler: ``` export default { async fetch(request, env, ctx) { return await fetch("https://api.postmarkapp.com/email", { method: "POST", headers: { "Content-Type": "application/json", "X-Postmark-Server-Token": env.POSTMARK_API_TOKEN, }, body: JSON.stringify({ From: "hello@example.com", To: "someone@example.com", Subject: "Hello World", HtmlBody: "

Hello from Workers

", }), }); }, }; ``` Explain Code And finally, deploy this update with `npm run deploy`. ## Related resources * [Storing API keys and tokens with Secrets](https://developers.cloudflare.com/workers/configuration/secrets/). * [Transferring your domain to Cloudflare](https://developers.cloudflare.com/registrar/get-started/transfer-domain-to-cloudflare/). * [Send emails from Workers](https://developers.cloudflare.com/email-routing/email-workers/send-email-workers/) ```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/tutorials/","name":"Tutorials"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/tutorials/send-emails-with-postmark/","name":"Send Emails With Postmark"}}]} ``` --- --- title: Send Emails With Resend description: This tutorial explains how to send emails from Cloudflare Workers using Resend. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) ### Tags [ JavaScript ](https://developers.cloudflare.com/search/?tags=JavaScript) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/tutorials/send-emails-with-resend.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Send Emails With Resend **Last reviewed:** almost 2 years ago In this tutorial, you will learn how to send transactional emails from Workers using [Resend ↗](https://resend.com/). At the end of this tutorial, you’ll be able to: * Create a Worker to send emails. * Sign up and add a Cloudflare domain to Resend. * Send emails from your Worker using Resend. * Store API keys securely with secrets. ## Prerequisites To continue with this tutorial, you’ll need: * A [Cloudflare account ↗](https://dash.cloudflare.com/sign-up/workers-and-pages), if you don’t already have one. * A [registered](https://developers.cloudflare.com/registrar/get-started/register-domain/) domain. * Installed [npm ↗](https://docs.npmjs.com/getting-started). * A [Resend account ↗](https://resend.com/signup). ## Create a Worker project Start by using [C3](https://developers.cloudflare.com/pages/get-started/c3/) to create a Worker project in the command line, then, answer the prompts: Terminal window ``` npm create cloudflare@latest ``` Alternatively, you can use CLI arguments to speed things up: Terminal window ``` npm create cloudflare@latest email-with-resend -- --type=hello-world --ts=false --git=true --deploy=false ``` This creates a simple hello-world Worker having the following content: JavaScript ``` export default { async fetch(request, env, ctx) { return new Response("Hello World!"); }, }; ``` ## Add your domain to Resend If you don’t already have a Resend account, you can sign up for a [free account here ↗](https://resend.com/signup). After signing up, go to `Domains` using the side menu, and click the button to add a new domain. On the modal, enter the domain you want to add and then select a region. Next, you’re presented with a list of DNS records to add to your Cloudflare domain. On your Cloudflare dashboard, select the domain you entered earlier and navigate to `DNS` \> `Records`. Copy/paste the DNS records (DKIM, SPF, and DMARC records) from Resend to your Cloudflare domain. ![Image of adding DNS records to a Cloudflare domain](https://developers.cloudflare.com/_astro/add_dns_records.Brij3X2H_3CIvl.webp) Note If you need more help adding DNS records in Cloudflare, refer to [Manage DNS records](https://developers.cloudflare.com/dns/manage-dns-records/how-to/create-dns-records/). When that’s done, head back to Resend and click on the `Verify DNS Records` button. If all records are properly configured, your domain status should be updated to `Verified`. ![Image of domain verification on the Resend dashboard](https://developers.cloudflare.com/_astro/verified_domain.ouYLJaQl_l764f.webp) Lastly, navigate to `API Keys` with the side menu, to create an API key. Give your key a descriptive name and the appropriate permissions. Click the button to add your key and then copy your API key to a safe location. ## Send emails from your Worker The final step is putting it all together in a Worker. Open up a terminal in the directory of the Worker you created earlier. Then, install the Resend SDK: Terminal window ``` npm i resend ``` In your Worker, import and use the Resend library like so: ``` import { Resend } from "resend"; export default { async fetch(request, env, ctx) { const resend = new Resend("your_resend_api_key"); const { data, error } = await resend.emails.send({ from: "hello@example.com", to: "someone@example.com", subject: "Hello World", html: "

Hello from Workers

", }); return Response.json({ data, error }); }, }; ``` Explain Code To test your code locally, run the following command and navigate to [http://localhost:8787/ ↗](http://localhost:8787/) in a browser: Terminal window ``` npm start ``` Deploy your Worker with `npm run deploy`. ## Move API keys to Secrets Sensitive information such as API keys and token should always be stored in secrets. All secrets are encrypted to add an extra layer of protection. That said, it’s a good idea to move your API key to a secret and access it from the environment of your Worker. To add secrets for local development, create a `.dev.vars` file which works exactly like a `.env` file: ``` RESEND_API_KEY=your_resend_api_key ``` Also ensure the secret is added to your deployed worker by running: Add secret to deployed Worker ``` npx wrangler secret put RESEND_API_KEY ``` The added secret can be accessed on via the `env` parameter passed to your Worker’s fetch event handler: ``` import { Resend } from "resend"; export default { async fetch(request, env, ctx) { const resend = new Resend(env.RESEND_API_KEY); const { data, error } = await resend.emails.send({ from: "hello@example.com", to: "someone@example.com", subject: "Hello World", html: "

Hello from Workers

", }); return Response.json({ data, error }); }, }; ``` Explain Code And finally, deploy this update with `npm run deploy`. ## Related resources * [Storing API keys and tokens with Secrets](https://developers.cloudflare.com/workers/configuration/secrets/). * [Transferring your domain to Cloudflare](https://developers.cloudflare.com/registrar/get-started/transfer-domain-to-cloudflare/). * [Send emails from Workers](https://developers.cloudflare.com/email-routing/email-workers/send-email-workers/) ```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/tutorials/","name":"Tutorials"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/tutorials/send-emails-with-resend/","name":"Send Emails With Resend"}}]} ``` --- --- title: Securely access and upload assets with Cloudflare R2 description: This tutorial explains how to create a TypeScript-based Cloudflare Workers project that can securely access files from and upload files to a CloudFlare R2 bucket. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) ### Tags [ TypeScript ](https://developers.cloudflare.com/search/?tags=TypeScript) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/tutorials/upload-assets-with-r2.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Securely access and upload assets with Cloudflare R2 **Last reviewed:** almost 3 years ago This tutorial explains how to create a TypeScript-based Cloudflare Workers project that can securely access files from and upload files to a [Cloudflare R2](https://developers.cloudflare.com/r2/) bucket. Cloudflare R2 allows developers to store large amounts of unstructured data without the costly egress bandwidth fees associated with typical cloud storage services. ## Prerequisites To continue: 1. Sign up for a [Cloudflare account ↗](https://dash.cloudflare.com/sign-up/workers-and-pages) if you have not already. 2. Install [npm ↗](https://docs.npmjs.com/getting-started). 3. Install [Node.js ↗](https://nodejs.org/en/). Use a Node version manager like [Volta ↗](https://volta.sh/) or [nvm ↗](https://github.com/nvm-sh/nvm) to avoid permission issues and change Node.js versions. [Wrangler](https://developers.cloudflare.com/workers/wrangler/install-and-update/) requires a Node version of `16.17.0` or later. ## Create a Worker application First, use the [create-cloudflare CLI ↗](https://github.com/cloudflare/workers-sdk/tree/main/packages/create-cloudflare) to create a new Worker. To do this, open a terminal window and run the following command: npm yarn pnpm ``` npm create cloudflare@latest -- upload-r2-assets ``` ``` yarn create cloudflare upload-r2-assets ``` ``` pnpm create cloudflare@latest upload-r2-assets ``` For setup, select the following options: * For _What would you like to start with?_, choose `Hello World example`. * For _Which template would you like to use?_, choose `Worker only`. * For _Which language do you want to use?_, choose `TypeScript`. * For _Do you want to use git for version control?_, choose `Yes`. * For _Do you want to deploy your application?_, choose `No` (we will be making some changes before deploying). Move into your newly created directory: Terminal window ``` cd upload-r2-assets ``` ## Create an R2 bucket Before you integrate R2 bucket access into your Worker application, an R2 bucket must be created: Terminal window ``` npx wrangler r2 bucket create ``` Replace `` with the name you want to assign to your bucket. List your account's R2 buckets to verify that a new bucket has been added: Terminal window ``` npx wrangler r2 bucket list ``` ## Configure access to an R2 bucket After your new R2 bucket is ready, use it inside your Worker application. Use your R2 bucket inside your Worker project by modifying the [Wrangler configuration file](https://developers.cloudflare.com/workers/wrangler/configuration/) to include an R2 bucket [binding](https://developers.cloudflare.com/workers/runtime-apis/bindings/). Add the following R2 bucket binding to your Wrangler file: * [ wrangler.jsonc ](#tab-panel-7962) * [ wrangler.toml ](#tab-panel-7963) JSONC ``` { "r2_buckets": [ { "binding": "MY_BUCKET", "bucket_name": "" } ] } ``` TOML ``` [[r2_buckets]] binding = "MY_BUCKET" bucket_name = "" ``` Give your R2 bucket binding name. Replace `` with the name of the R2 bucket you created earlier. Your Worker application can now access your R2 bucket using the `MY_BUCKET` variable. You can now perform CRUD (Create, Read, Update, Delete) operations on the contents of the bucket. ## Fetch from an R2 bucket After setting up an R2 bucket binding, you will implement the functionalities for the Worker to interact with the R2 bucket, such as, fetching files from the bucket and uploading files to the bucket. To fetch files from the R2 bucket, use the `BINDING.get` function. In the below example, the R2 bucket binding is called `MY_BUCKET`. Using `.get(key)`, you can retrieve an asset based on the URL pathname as the key. In this example, the URL pathname is `/image.png`, and the asset key is `image.png`. TypeScript ``` interface Env { MY_BUCKET: R2Bucket; } export default { async fetch(request, env): Promise { // For example, the request URL my-worker.account.workers.dev/image.png const url = new URL(request.url); const key = url.pathname.slice(1); // Retrieve the key "image.png" const object = await env.MY_BUCKET.get(key); if (object === null) { return new Response("Object Not Found", { status: 404 }); } const headers = new Headers(); object.writeHttpMetadata(headers); headers.set("etag", object.httpEtag); return new Response(object.body, { headers, }); }, } satisfies ExportedHandler; ``` Explain Code The code written above fetches and returns data from the R2 bucket when a `GET` request is made to the Worker application using a specific URL path. ## Upload securely to an R2 bucket Next, you will add the ability to upload to your R2 bucket using authentication. To securely authenticate your upload requests, use [Wrangler's secret capability](https://developers.cloudflare.com/workers/wrangler/commands/general/#secret). Wrangler was installed when you ran the `create cloudflare@latest` command. Create a secret value of your choice -- for instance, a random string or password. Using the Wrangler CLI, add the secret to your project as `AUTH_SECRET`: Terminal window ``` npx wrangler secret put AUTH_SECRET ``` Now, add a new code path that handles a `PUT` HTTP request. This new code will check that the previously uploaded secret is correctly used for authentication, and then upload to R2 using `MY_BUCKET.put(key, data)`: TypeScript ``` interface Env { MY_BUCKET: R2Bucket; AUTH_SECRET: string; } export default { async fetch(request, env): Promise { if (request.method === "PUT") { // Note that you could require authentication for all requests // by moving this code to the top of the fetch function. const auth = request.headers.get("Authorization"); const expectedAuth = `Bearer ${env.AUTH_SECRET}`; if (!auth || auth !== expectedAuth) { return new Response("Unauthorized", { status: 401 }); } const url = new URL(request.url); const key = url.pathname.slice(1); await env.MY_BUCKET.put(key, request.body); return new Response(`Object ${key} uploaded successfully!`); } // include the previous code here... }, } satisfies ExportedHandler; ``` Explain Code This approach ensures that only clients who provide a valid bearer token, via the `Authorization` header equal to the `AUTH_SECRET` value, will be permitted to upload to the R2 bucket. If you used a different binding name than `AUTH_SECRET`, replace it in the code above. ## Deploy your Worker application After completing your Cloudflare Worker project, deploy it to Cloudflare. Make sure you are in your Worker application directory that you created for this tutorial, then run: Terminal window ``` npx wrangler deploy ``` Your application is now live and accessible at `..workers.dev`. You have successfully created a Cloudflare Worker that allows you to interact with an R2 bucket to accomplish tasks such as uploading and downloading files. You can now use this as a starting point for your own projects. ## Next steps To build more with R2 and Workers, refer to [Tutorials](https://developers.cloudflare.com/workers/tutorials/) and the [R2 documentation](https://developers.cloudflare.com/r2/). If you have any questions, need assistance, or would like to share your project, join the Cloudflare Developer community on [Discord ↗](https://discord.cloudflare.com) to connect with fellow developers and the Cloudflare team. ```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/tutorials/","name":"Tutorials"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/tutorials/upload-assets-with-r2/","name":"Securely access and upload assets with Cloudflare R2"}}]} ``` --- --- title: Set up and use a Prisma Postgres database description: This tutorial shows you how to set up a Cloudflare Workers project with Prisma ORM. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) ### Tags [ TypeScript ](https://developers.cloudflare.com/search/?tags=TypeScript)[ SQL ](https://developers.cloudflare.com/search/?tags=SQL)[ Prisma ORM ](https://developers.cloudflare.com/search/?tags=Prisma%20ORM)[ Postgres ](https://developers.cloudflare.com/search/?tags=Postgres) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/tutorials/using-prisma-postgres-with-workers.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Set up and use a Prisma Postgres database **Last reviewed:** about 1 year ago [Prisma Postgres ↗](https://www.prisma.io/postgres) is a managed, serverless PostgreSQL database. It supports features like connection pooling, caching, real-time subscriptions, and query optimization recommendations. In this tutorial, you will learn how to: * Set up a Cloudflare Workers project with [Prisma ORM ↗](https://www.prisma.io/docs). * Create a Prisma Postgres instance from the Prisma CLI. * Model data and run migrations with Prisma Postgres. * Query the database from Workers. * Deploy the Worker to Cloudflare. ## Prerequisites To follow this guide, ensure you have the following: * Node.js `v18.18` or higher installed. * An active [Cloudflare account ↗](https://dash.cloudflare.com/). * A basic familiarity with installing and using command-line interface (CLI) applications. ## 1\. Create a new Worker project Begin by using [C3](https://developers.cloudflare.com/pages/get-started/c3/) to create a Worker project in the command line: Terminal window ``` npm create cloudflare@latest prisma-postgres-worker -- --type=hello-world --ts=true --git=true --deploy=false ``` Then navigate into your project: Terminal window ``` cd ./prisma-postgres-worker ``` Your initial `src/index.ts` file currently contains a simple request handler: src/index.ts ``` export default { async fetch(request, env, ctx): Promise { return new Response("Hello World!"); }, } satisfies ExportedHandler; ``` ## 2\. Setup Prisma in your project In this step, you will set up Prisma ORM with a Prisma Postgres database using the CLI. Then you will create and execute helper scripts to create tables in the database and generate a Prisma client to query it. ### 2.1\. Install required dependencies Install Prisma CLI as a dev dependency: npm yarn pnpm bun ``` npm i -D prisma ``` ``` yarn add -D prisma ``` ``` pnpm add -D prisma ``` ``` bun add -d prisma ``` Install the [Prisma Accelerate client extension ↗](https://www.npmjs.com/package/@prisma/extension-accelerate) as it is required for Prisma Postgres: npm yarn pnpm bun ``` npm i @prisma/extension-accelerate ``` ``` yarn add @prisma/extension-accelerate ``` ``` pnpm add @prisma/extension-accelerate ``` ``` bun add @prisma/extension-accelerate ``` Install the [dotenv-cli package ↗](https://www.npmjs.com/package/dotenv-cli) to load environment variables from `.dev.vars`: npm yarn pnpm bun ``` npm i -D dotenv-cli ``` ``` yarn add -D dotenv-cli ``` ``` pnpm add -D dotenv-cli ``` ``` bun add -d dotenv-cli ``` ### 2.2\. Create a Prisma Postgres database and initialize Prisma Initialize Prisma in your application: npm yarn pnpm ``` npx prisma@latest init --db ``` ``` yarn dlx prisma@latest init --db ``` ``` pnpx prisma@latest init --db ``` If you do not have a [Prisma Data Platform ↗](https://console.prisma.io/) account yet, or if you are not logged in, the command will prompt you to log in using one of the available authentication providers. A browser window will open so you can log in or create an account. Return to the CLI after you have completed this step. Once logged in (or if you were already logged in), the CLI will prompt you to select a project name and a database region. Once the command has terminated, it will have created: * A project in your [Platform Console ↗](https://console.prisma.io/) containing a Prisma Postgres database instance. * A `prisma` folder containing `schema.prisma`, where you will define your database schema. * An `.env` file in the project root, which will contain the Prisma Postgres database url `DATABASE_URL=`. Note that Cloudflare Workers do not support `.env` files. You will use a file called `.dev.vars` instead of the `.env` file that was just created. ### 2.3\. Prepare environment variables Rename the `.env` file in the root of your application to `.dev.vars` file: Terminal window ``` mv .env .dev.vars ``` ### 2.4\. Apply database schema changes Open the `schema.prisma` file in the `prisma` folder and add the following `User` model to your database: prisma/schema.prisma ``` generator client { provider = "prisma-client-js" } datasource db { provider = "postgresql" url = env("DATABASE_URL") } model User { id Int @id @default(autoincrement()) email String name String } ``` Explain Code Next, add the following helper scripts to the `scripts` section of your `package.json`: package.json ``` "scripts": { "migrate": "dotenv -e .dev.vars -- npx prisma migrate dev", "generate": "dotenv -e .dev.vars -- npx prisma generate --no-engine", "studio": "dotenv -e .dev.vars -- npx prisma studio", // Additional worker scripts... } ``` Run the migration script to apply changes to the database: Terminal window ``` npm run migrate ``` When prompted, provide a name for the migration (for example, `init`). After these steps are complete, Prisma ORM is fully set up and connected to your Prisma Postgres database. ## 3\. Develop the application Modify the `src/index.ts` file and replace its contents with the following code: src/index.ts ``` import { PrismaClient } from "@prisma/client/edge"; import { withAccelerate } from "@prisma/extension-accelerate"; export interface Env { DATABASE_URL: string; } export default { async fetch(request, env, ctx): Promise { const path = new URL(request.url).pathname; if (path === "/favicon.ico") return new Response("Resource not found", { status: 404, headers: { "Content-Type": "text/plain", }, }); const prisma = new PrismaClient({ datasourceUrl: env.DATABASE_URL, }).$extends(withAccelerate()); const user = await prisma.user.create({ data: { email: `Jon${Math.ceil(Math.random() * 1000)}@gmail.com`, name: "Jon Doe", }, }); const userCount = await prisma.user.count(); return new Response(`\ Created new user: ${user.name} (${user.email}). Number of users in the database: ${userCount}. `); }, } satisfies ExportedHandler; ``` Explain Code Run the development server: Terminal window ``` npm run dev ``` Visit [https://localhost:8787 ↗](https://localhost:8787) to see your app display the following output: Terminal window ``` Number of users in the database: 1 ``` Every time you refresh the page, a new user is created. The number displayed will increment by `1` with each refresh as it returns the total number of users in your database. ## 4\. Deploy the application to Cloudflare When the application is deployed to Cloudflare, it needs access to the `DATABASE_URL` environment variable that is defined locally in `.dev.vars`. You can use the [npx wrangler secret put](https://developers.cloudflare.com/workers/configuration/secrets/#adding-secrets-to-your-project) command to upload the `DATABASE_URL` to the deployment environment: Terminal window ``` npx wrangler secret put DATABASE_URL ``` When prompted, paste the `DATABASE_URL` value (from `.dev.vars`). If you are logged in via the Wrangler CLI, you will see a prompt asking if you'd like to create a new Worker. Confirm by choosing "yes": Terminal window ``` ✔ There doesn't seem to be a Worker called "prisma-postgres-worker". Do you want to create a new Worker with that name and add secrets to it? … yes ``` Then execute the following command to deploy your project to Cloudflare Workers: Terminal window ``` npm run deploy ``` The `wrangler` CLI will bundle and upload your application. If you are not already logged in, the `wrangler` CLI will open a browser window prompting you to log in to the Cloudflare dashboard. Note If you belong to multiple accounts, select the account where you want to deploy the project. Once the deployment completes, verify the deployment by visiting the live URL provided in the deployment output, such as `https://{PROJECT_NAME}.workers.dev`. If you encounter any issues, ensure the secrets were added correctly and check the deployment logs for errors. ## Next steps Congratulations on building and deploying a simple application with Prisma Postgres and Cloudflare Workers! To enhance your application further: * Add [caching ↗](https://www.prisma.io/docs/postgres/caching) to your queries. * Explore the [Prisma Postgres documentation ↗](https://www.prisma.io/docs/postgres/getting-started). To see how to build a real-time application with Cloudflare Workers and Prisma Postgres, read [this ↗](https://www.prisma.io/docs/guides/prisma-postgres-realtime-on-cloudflare) guide. ```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/tutorials/","name":"Tutorials"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/tutorials/using-prisma-postgres-with-workers/","name":"Set up and use a Prisma Postgres database"}}]} ``` --- --- title: Use Workers KV directly from Rust description: This tutorial will teach you how to read and write to KV directly from Rust using workers-rs. You will use Workers KV from Rust to build an app to store and retrieve cities. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) ### Tags [ Rust ](https://developers.cloudflare.com/search/?tags=Rust) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/tutorials/workers-kv-from-rust.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Use Workers KV directly from Rust **Last reviewed:** almost 2 years ago This tutorial will teach you how to read and write to KV directly from Rust using [workers-rs ↗](https://github.com/cloudflare/workers-rs). ## Before you start All of the tutorials assume you have already completed the [Get started guide](https://developers.cloudflare.com/workers/get-started/guide/), which gets you set up with a Cloudflare Workers account, [C3 ↗](https://github.com/cloudflare/workers-sdk/tree/main/packages/create-cloudflare), and [Wrangler](https://developers.cloudflare.com/workers/wrangler/install-and-update/). ## Prerequisites To complete this tutorial, you will need: * [Git ↗](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git). * [Wrangler](https://developers.cloudflare.com/workers/wrangler/) CLI. * The [Rust ↗](https://www.rust-lang.org/tools/install) toolchain. * And `cargo-generate` sub-command by running: Terminal window ``` cargo install cargo-generate ``` ## 1\. Create your Worker project in Rust Open a terminal window, and run the following command to generate a Worker project template in Rust: Terminal window ``` cargo generate cloudflare/workers-rs ``` Then select `template/hello-world-http` template, give your project a descriptive name and select enter. A new project should be created in your directory. Open the project in your editor and run `npx wrangler dev` to compile and run your project. In this tutorial, you will use Workers KV from Rust to build an app to store and retrieve cities by a given country name. ## 2\. Create a KV namespace In the terminal, use Wrangler to create a KV namespace for `cities`. This generates a configuration to be added to the project: Terminal window ``` npx wrangler kv namespace create cities ``` To add this configuration to your project, open the Wrangler file and create an entry for `kv_namespaces` above the build command: * [ wrangler.jsonc ](#tab-panel-7964) * [ wrangler.toml ](#tab-panel-7965) JSONC ``` { "kv_namespaces": [ { "binding": "cities", "id": "e29b263ab50e42ce9b637fa8370175e8" } ] } ``` TOML ``` [[kv_namespaces]] binding = "cities" id = "e29b263ab50e42ce9b637fa8370175e8" ``` With this configured, you can access the KV namespace with the binding `"cities"` from Rust. ## 3\. Write data to KV For this app, you will create two routes: A `POST` route to receive and store the city in KV, and a `GET` route to retrieve the city of a given country. For example, a `POST` request to `/France` with a body of `{"city": "Paris"}` should create an entry of Paris as a city in France. A `GET` request to `/France` should retrieve from KV and respond with Paris. Install [Serde ↗](https://serde.rs/) as a project dependency to handle JSON `cargo add serde`. Then create an app router and a struct for `Country` in `src/lib.rs`: ``` use serde::{Deserialize, Serialize}; use worker::*; #[event(fetch)] async fn fetch(req: Request, env: Env, _ctx: Context) -> Result { let router = Router::new(); #[derive(Serialize, Deserialize, Debug)] struct Country { city: String, } router // TODO: .post_async("/:country", |_, _| async move { Response::empty() }) // TODO: .get_async("/:country", |_, _| async move { Response::empty() }) .run(req, env) .await } ``` Explain Code For the post handler, you will retrieve the country name from the path and the city name from the request body. Then, you will save this in KV with the country as key and the city as value. Finally, the app will respond with the city name: ``` .post_async("/:country", |mut req, ctx| async move { let country = ctx.param("country").unwrap(); let city = match req.json::().await { Ok(c) => c.city, Err(_) => String::from(""), }; if city.is_empty() { return Response::error("Bad Request", 400); }; return match ctx.kv("cities")?.put(country, &city)?.execute().await { Ok(_) => Response::ok(city), Err(_) => Response::error("Bad Request", 400), }; }) ``` Explain Code Save the file and make a `POST` request to test this endpoint: Terminal window ``` curl --json '{"city": "Paris"}' http://localhost:8787/France ``` ## 4\. Read data from KV To retrieve cities stored in KV, write a `GET` route that pulls the country name from the path and searches KV. You also need some error handling if the country is not found: ``` .get_async("/:country", |_req, ctx| async move { if let Some(country) = ctx.param("country") { return match ctx.kv("cities")?.get(country).text().await? { Some(city) => Response::ok(city), None => Response::error("Country not found", 404), }; } Response::error("Bad Request", 400) }) ``` Save and make a curl request to test the endpoint: Terminal window ``` curl http://localhost:8787/France ``` ## 5\. Deploy your project The source code for the completed app should include the following: ``` use serde::{Deserialize, Serialize}; use worker::*; #[event(fetch)] async fn fetch(req: Request, env: Env, _ctx: Context) -> Result { let router = Router::new(); #[derive(Serialize, Deserialize, Debug)] struct Country { city: String, } router .post_async("/:country", |mut req, ctx| async move { let country = ctx.param("country").unwrap(); let city = match req.json::().await { Ok(c) => c.city, Err(_) => String::from(""), }; if city.is_empty() { return Response::error("Bad Request", 400); }; return match ctx.kv("cities")?.put(country, &city)?.execute().await { Ok(_) => Response::ok(city), Err(_) => Response::error("Bad Request", 400), }; }) .get_async("/:country", |_req, ctx| async move { if let Some(country) = ctx.param("country") { return match ctx.kv("cities")?.get(country).text().await? { Some(city) => Response::ok(city), None => Response::error("Country not found", 404), }; } Response::error("Bad Request", 400) }) .run(req, env) .await } ``` Explain Code To deploy your Worker, run the following command: Terminal window ``` npx wrangler deploy ``` ## Related resources * [Rust support in Workers](https://developers.cloudflare.com/workers/languages/rust/). * [Using KV in Workers](https://developers.cloudflare.com/kv/get-started/). ```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/tutorials/","name":"Tutorials"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/tutorials/workers-kv-from-rust/","name":"Use Workers KV directly from Rust"}}]} ``` --- --- title: Demos and architectures description: Learn how you can use Workers within your existing application and architecture. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/demos.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Demos and architectures Learn how you can use Workers within your existing application and architecture. ## Demos Explore the following demo applications for Workers. * [Starter code for D1 Sessions API: ↗](https://github.com/cloudflare/templates/tree/main/d1-starter-sessions-api-template) An introduction to D1 Sessions API. This demo simulates purchase orders administration. * [JavaScript-native RPC on Cloudflare Workers <> Named Entrypoints: ↗](https://github.com/cloudflare/js-rpc-and-entrypoints-demo) This is a collection of examples of communicating between multiple Cloudflare Workers using the remote-procedure call (RPC) system that is built into the Workers runtime. * [Workers for Platforms Example Project: ↗](https://github.com/cloudflare/workers-for-platforms-example) Explore how you could manage thousands of Workers with a single Cloudflare Workers account. * [Cloudflare Workers Chat Demo: ↗](https://github.com/cloudflare/workers-chat-demo) This is a demo app written on Cloudflare Workers utilizing Durable Objects to implement real-time chat with stored history. * [Turnstile Demo: ↗](https://github.com/cloudflare/turnstile-demo-workers) A simple demo with a Turnstile-protected form, using Cloudflare Workers. With the code in this repository, we demonstrate implicit rendering and explicit rendering. * [Wildebeest: ↗](https://github.com/cloudflare/wildebeest) Wildebeest is an ActivityPub and Mastodon-compatible server whose goal is to allow anyone to operate their Fediverse server and identity on their domain without needing to keep infrastructure, with minimal setup and maintenance, and running in minutes. * [D1 Northwind Demo: ↗](https://github.com/cloudflare/d1-northwind) This is a demo of the Northwind dataset, running on Cloudflare Workers, and D1 - Cloudflare's SQL database, running on SQLite. * [Multiplayer Doom Workers: ↗](https://github.com/cloudflare/doom-workers) A WebAssembly Doom port with multiplayer support running on top of Cloudflare's global network using Workers, WebSockets, Pages, and Durable Objects. * [Queues Web Crawler: ↗](https://github.com/cloudflare/queues-web-crawler) An example use-case for Queues, a web crawler built on Browser Rendering and Puppeteer. The crawler finds the number of links to Cloudflare.com on the site, and archives a screenshot to Workers KV. * [DMARC Email Worker: ↗](https://github.com/cloudflare/dmarc-email-worker) A Cloudflare worker script to process incoming DMARC reports, store them, and produce analytics. * [Access External Auth Rule Example Worker: ↗](https://github.com/cloudflare/workers-access-external-auth-example) This is a worker that allows you to quickly setup an external evalutation rule in Cloudflare Access. ## Reference architectures Explore the following reference architectures that use Workers: [Fullstack applicationsA practical example of how these services come together in a real fullstack application architecture.](https://developers.cloudflare.com/reference-architecture/diagrams/serverless/fullstack-application/)[Storing user generated contentStore user-generated content in R2 for fast, secure, and cost-effective architecture.](https://developers.cloudflare.com/reference-architecture/diagrams/storage/storing-user-generated-content/)[Optimizing and securing connected transportation systemsThis diagram showcases Cloudflare components optimizing connected transportation systems. It illustrates how their technologies minimize latency, ensure reliability, and strengthen security for critical data flow.](https://developers.cloudflare.com/reference-architecture/diagrams/iot/optimizing-and-securing-connected-transportation-systems/)[Ingesting BigQuery Data into Workers AIYou can connect a Cloudflare Worker to get data from Google BigQuery and pass it to Workers AI, to run AI Models, powered by serverless GPUs.](https://developers.cloudflare.com/reference-architecture/diagrams/ai/bigquery-workers-ai/)[Event notifications for storageUse Cloudflare Workers or an external service to monitor for notifications about data changes and then handle them appropriately.](https://developers.cloudflare.com/reference-architecture/diagrams/storage/event-notifications-for-storage/)[Extend ZTNA with external authorization and serverless computingCloudflare's ZTNA enhances access policies using external API calls and Workers for robust security. It verifies user authentication and authorization, ensuring only legitimate access to protected resources.](https://developers.cloudflare.com/reference-architecture/diagrams/sase/augment-access-with-serverless/)[Cloudflare Security ArchitectureThis document provides insight into how this network and platform are architected from a security perspective, how they are operated, and what services are available for businesses to address their own security challenges.](https://developers.cloudflare.com/reference-architecture/architectures/security/)[Composable AI architectureThe architecture diagram illustrates how AI applications can be built end-to-end on Cloudflare, or single services can be integrated with external infrastructure and services.](https://developers.cloudflare.com/reference-architecture/diagrams/ai/ai-composable/)[A/B-testing using WorkersCloudflare's low-latency, fully serverless compute platform, Workers offers powerful capabilities to enable A/B testing using a server-side implementation.](https://developers.cloudflare.com/reference-architecture/diagrams/serverless/a-b-testing-using-workers/)[Serverless global APIsAn example architecture of a serverless API on Cloudflare and aims to illustrate how different compute and data products could interact with each other.](https://developers.cloudflare.com/reference-architecture/diagrams/serverless/serverless-global-apis/)[Serverless ETL pipelinesCloudflare enables fully serverless ETL pipelines, significantly reducing complexity, accelerating time to production, and lowering overall costs.](https://developers.cloudflare.com/reference-architecture/diagrams/serverless/serverless-etl/)[Egress-free object storage in multi-cloud setupsLearn how to use R2 to get egress-free object storage in multi-cloud setups.](https://developers.cloudflare.com/reference-architecture/diagrams/storage/egress-free-storage-multi-cloud/)[Retrieval Augmented Generation (RAG)RAG combines retrieval with generative models for better text. It uses external knowledge to create factual, relevant responses, improving coherence and accuracy in NLP tasks like chatbots.](https://developers.cloudflare.com/reference-architecture/diagrams/ai/ai-rag/)[Automatic captioning for video uploadsBy integrating automatic speech recognition technology into video platforms, content creators, publishers, and distributors can reach a broader audience, including individuals with hearing impairments or those who prefer to consume content in different languages.](https://developers.cloudflare.com/reference-architecture/diagrams/ai/ai-video-caption/)[Serverless image content managementLeverage various components of Cloudflare's ecosystem to construct a scalable image management solution](https://developers.cloudflare.com/reference-architecture/diagrams/serverless/serverless-image-content-management/) ```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/demos/","name":"Demos and architectures"}}]} ``` --- --- title: Development & testing description: Develop and test your Workers locally. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/development-testing/index.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Development & testing You can build, run, and test your Worker code on your own local machine before deploying it to Cloudflare's network. This is made possible through [Miniflare](https://developers.cloudflare.com/workers/testing/miniflare/), a simulator that executes your Worker code using the same runtime used in production, [workerd ↗](https://github.com/cloudflare/workerd). [By default](https://developers.cloudflare.com/workers/development-testing/#defaults), your Worker's bindings [connect to locally simulated resources](https://developers.cloudflare.com/workers/development-testing/#bindings-during-local-development), but can be configured to interact with the real, production resource with [remote bindings](https://developers.cloudflare.com/workers/development-testing/#remote-bindings). ## Core concepts ### Worker execution vs Bindings When developing Workers, it's important to understand two distinct concepts: * **Worker execution**: Where your Worker code actually runs (on your local machine vs on Cloudflare's infrastructure). * [**Bindings**](https://developers.cloudflare.com/workers/runtime-apis/bindings/): How your Worker interacts with Cloudflare resources (like [KV namespaces](https://developers.cloudflare.com/kv), [R2 buckets](https://developers.cloudflare.com/r2), [D1 databases](https://developers.cloudflare.com/d1), [Queues](https://developers.cloudflare.com/queues/), [Durable Objects](https://developers.cloudflare.com/durable-objects/), etc). In your Worker code, these are accessed via the `env` object (such as `env.MY_KV`). ## Local development **You can start a local development server using:** 1. The Cloudflare Workers CLI [**Wrangler**](https://developers.cloudflare.com/workers/wrangler/), using the built-in [wrangler dev](https://developers.cloudflare.com/workers/wrangler/commands/general/#dev) command. npm yarn pnpm ``` npx wrangler dev ``` ``` yarn wrangler dev ``` ``` pnpm wrangler dev ``` 1. [**Vite** ↗](https://vite.dev/), using the [**Cloudflare Vite plugin**](https://developers.cloudflare.com/workers/vite-plugin/). npm yarn pnpm ``` npx vite dev ``` ``` yarn vite dev ``` ``` pnpm vite dev ``` Both Wrangler and the Cloudflare Vite plugin use [Miniflare](https://developers.cloudflare.com/workers/testing/miniflare/) under the hood, and are developed and maintained by the Cloudflare team. For guidance on choosing when to use Wrangler versus Vite, see our guide [Choosing between Wrangler & Vite](https://developers.cloudflare.com/workers/development-testing/wrangler-vs-vite/). * [Get started with Wrangler](https://developers.cloudflare.com/workers/wrangler/install-and-update/) * [Get started with the Cloudflare Vite plugin](https://developers.cloudflare.com/workers/vite-plugin/get-started/) ### Defaults By default, running `wrangler dev` / `vite dev` (when using the [Vite plugin](https://developers.cloudflare.com/workers/vite-plugin/get-started/)) means that: * Your Worker code runs on your local machine. * All resources your Worker is bound to in your [Wrangler configuration](https://developers.cloudflare.com/workers/wrangler/configuration/) are simulated locally. ### Bindings during local development [Bindings](https://developers.cloudflare.com/workers/runtime-apis/bindings/) are interfaces that allow your Worker to interact with various Cloudflare resources (like [KV namespaces](https://developers.cloudflare.com/kv), [R2 buckets](https://developers.cloudflare.com/r2), [D1 databases](https://developers.cloudflare.com/d1), [Queues](https://developers.cloudflare.com/queues/), [Durable Objects](https://developers.cloudflare.com/durable-objects/), etc). In your Worker code, these are accessed via the `env` object (such as `env.MY_KV`). During local development, your Worker code interacts with these bindings using the exact same API calls (such as `env.MY_KV.put()`) as it would in a deployed environment. These local resources are initially empty, but you can populate them with data, as documented in [Adding local data](https://developers.cloudflare.com/workers/development-testing/local-data/). * By default, bindings connect to **local resource simulations** (except for [AI bindings](https://developers.cloudflare.com/workers-ai/configuration/bindings/), as AI models always run remotely). * You can override this default behavior and **connect to the remote resource** on a per-binding basis with [remote bindings](https://developers.cloudflare.com/workers/development-testing/#remote-bindings). This lets you connect to real, production resources while still running your Worker code locally. * When using `wrangler dev`, you can temporarily disable all [remote bindings](https://developers.cloudflare.com/workers/development-testing/#remote-bindings) (and connect only to local resources) by providing the `--local` flag (i.e. `wrangler dev --local`) ## Remote bindings **Remote bindings** are bindings that are configured to connect to the deployed, remote resource during local development _instead_ of the locally simulated resource. Remote bindings are supported by [**Wrangler**](https://developers.cloudflare.com/workers/wrangler/), the [**Cloudflare Vite plugin**](https://developers.cloudflare.com/workers/vite-plugin/), and the `@cloudflare/vitest-pool-workers` package. You can configure remote bindings by setting `remote: true` in the binding definition. ### Example configuration * [ wrangler.jsonc ](#tab-panel-7329) * [ wrangler.toml ](#tab-panel-7330) JSONC ``` { "name": "my-worker", // Set this to today's date "compatibility_date": "2026-04-14", "r2_buckets": [ { "bucket_name": "screenshots-bucket", "binding": "screenshots_bucket", "remote": true, }, ], } ``` Explain Code TOML ``` name = "my-worker" # Set this to today's date compatibility_date = "2026-04-14" [[r2_buckets]] bucket_name = "screenshots-bucket" binding = "screenshots_bucket" remote = true ``` When remote bindings are configured, your Worker still **executes locally**, only the underlying resources your bindings connect to change. For all bindings marked with `remote: true`, Miniflare will route its operations (such as `env.MY_KV.put()`) to the deployed resource. All other bindings not explicitly configured with `remote: true` continue to use their default local simulations. ### Integration with environments Remote Bindings work well together with [Workers Environments](https://developers.cloudflare.com/workers/wrangler/environments). To protect production data, you can create a development or staging environment and specify different resources in your [Wrangler configuration](https://developers.cloudflare.com/workers/wrangler/configuration/) than you would use for production. **For example:** * [ wrangler.jsonc ](#tab-panel-7341) * [ wrangler.toml ](#tab-panel-7342) JSONC ``` { "name": "my-worker", // Set this to today's date "compatibility_date": "2026-04-14", "env": { "production": { "r2_buckets": [ { "bucket_name": "screenshots-bucket", "binding": "screenshots_bucket", }, ], }, "staging": { "r2_buckets": [ { "bucket_name": "preview-screenshots-bucket", "binding": "screenshots_bucket", "remote": true, }, ], }, }, } ``` Explain Code TOML ``` name = "my-worker" # Set this to today's date compatibility_date = "2026-04-14" [[env.production.r2_buckets]] bucket_name = "screenshots-bucket" binding = "screenshots_bucket" [[env.staging.r2_buckets]] bucket_name = "preview-screenshots-bucket" binding = "screenshots_bucket" remote = true ``` Explain Code Running `wrangler dev -e staging` (or `CLOUDFLARE_ENV=staging vite dev`) with the above configuration means that: * Your Worker code runs locally * All calls made to `env.screenshots_bucket` will use the `preview-screenshots-bucket` resource, rather than the production `screenshots-bucket`. ### Recommended remote bindings We recommend configuring specific bindings to connect to their remote counterparts. These services often rely on Cloudflare's network infrastructure or have complex backends that are not fully simulated locally. The following bindings are recommended to have `remote: true` in your Wrangler configuration: #### [Browser Rendering](https://developers.cloudflare.com/workers/wrangler/configuration/#browser-rendering): To interact with a real headless browser for rendering. There is no current local simulation for Browser Rendering. * [ wrangler.jsonc ](#tab-panel-7327) * [ wrangler.toml ](#tab-panel-7328) JSONC ``` { "browser": { "binding": "MY_BROWSER", "remote": true }, } ``` TOML ``` [browser] binding = "MY_BROWSER" remote = true ``` #### [Workers AI](https://developers.cloudflare.com/workers/wrangler/configuration/#workers-ai): To utilize actual AI models deployed on Cloudflare's network for inference. There is no current local simulation for Workers AI. * [ wrangler.jsonc ](#tab-panel-7331) * [ wrangler.toml ](#tab-panel-7332) JSONC ``` { "ai": { "binding": "AI", "remote": true }, } ``` TOML ``` [ai] binding = "AI" remote = true ``` #### [Vectorize](https://developers.cloudflare.com/workers/wrangler/configuration/#vectorize-indexes): To connect to your production Vectorize indexes for accurate vector search and similarity operations. There is no current local simulation for Vectorize. * [ wrangler.jsonc ](#tab-panel-7333) * [ wrangler.toml ](#tab-panel-7334) JSONC ``` { "vectorize": [ { "binding": "MY_VECTORIZE_INDEX", "index_name": "my-prod-index", "remote": true } ], } ``` TOML ``` [[vectorize]] binding = "MY_VECTORIZE_INDEX" index_name = "my-prod-index" remote = true ``` #### [mTLS](https://developers.cloudflare.com/workers/wrangler/configuration/#mtls-certificates): To verify that the certificate exchange and validation process work as expected. There is no current local simulation for mTLS bindings. * [ wrangler.jsonc ](#tab-panel-7337) * [ wrangler.toml ](#tab-panel-7338) JSONC ``` { "mtls_certificates": [ { "binding": "MY_CLIENT_CERT_FETCHER", "certificate_id": "", "remote": true } ] } ``` TOML ``` [[mtls_certificates]] binding = "MY_CLIENT_CERT_FETCHER" certificate_id = "" remote = true ``` #### [Images](https://developers.cloudflare.com/workers/wrangler/configuration/#images): To connect to a high-fidelity version of the Images API, and verify that all transformations work as expected. Local simulation for Cloudflare Images is [limited with only a subset of features](https://developers.cloudflare.com/images/transform-images/bindings/#interact-with-your-images-binding-locally). * [ wrangler.jsonc ](#tab-panel-7335) * [ wrangler.toml ](#tab-panel-7336) JSONC ``` { "images": { "binding": "IMAGES" , "remote": true } } ``` TOML ``` [images] binding = "IMAGES" remote = true ``` Note If `remote: true` is not specified for Browser Rendering, Vectorize, mTLS, or Images, Cloudflare **will issue a warning**. This prompts you to consider enabling it for a more production-like testing experience. If a Workers AI binding has `remote` set to `false`, Cloudflare will **produce an error**. If the property is omitted, Cloudflare will connect to the remote resource and issue a warning to add the property to configuration. #### [Dispatch Namespaces](https://developers.cloudflare.com/cloudflare-for-platforms/workers-for-platforms/reference/local-development/): Workers for Platforms users can configure `remote: true` in dispatch namespace binding definitions: * [ wrangler.jsonc ](#tab-panel-7339) * [ wrangler.toml ](#tab-panel-7340) JSONC ``` { "dispatch_namespaces": [ { "binding": "DISPATCH_NAMESPACE", "namespace": "testing", "remote":true } ] } ``` TOML ``` [[dispatch_namespaces]] binding = "DISPATCH_NAMESPACE" namespace = "testing" remote = true ``` This allows you to run your [dynamic dispatch Worker](https://developers.cloudflare.com/cloudflare-for-platforms/workers-for-platforms/how-workers-for-platforms-works/#dynamic-dispatch-worker) locally, while connecting it to your remote dispatch namespace binding. This allows you to test changes to your core dispatching logic against real, deployed [user Workers](https://developers.cloudflare.com/cloudflare-for-platforms/workers-for-platforms/how-workers-for-platforms-works/#user-workers). ### Unsupported remote bindings Certain bindings are not supported for remote connections (i.e. with `remote: true`) during local development. These will always use local simulations or local values. If `remote: true` is specified in Wrangler configuration for any of the following unsupported binding types, Cloudflare **will issue an error**. See [all supported and unsupported bindings for remote bindings](https://developers.cloudflare.com/workers/development-testing/bindings-per-env/). * [**Durable Objects**](https://developers.cloudflare.com/workers/wrangler/configuration/#durable-objects): Enabling remote connections for Durable Objects may be supported in the future, but currently will always run locally. However, using Durable Objects in combination with remote bindings is possible. Refer to [Using remote resources with Durable Objects and Workflows](#using-remote-resources-with-durable-objects-and-workflows) below. * [**Workflows**](https://developers.cloudflare.com/workflows/): Enabling remote connections for Workflows may be supported in the future, but currently will only run locally. However, using Workflows in combination with remote bindings is possible. Refer to [Using remote resources with Durable Objects and Workflows](#using-remote-resources-with-durable-objects-and-workflows) below. * [**Environment Variables (vars)**](https://developers.cloudflare.com/workers/wrangler/configuration/#environment-variables): Environment variables are intended to be distinct between local development and deployed environments. They are easily configurable locally (such as in a `.dev.vars` file or directly in Wrangler configuration). * [**Secrets**](https://developers.cloudflare.com/workers/wrangler/configuration/#secrets): Like environment variables, secrets are expected to have different values in local development versus deployed environments for security reasons. Use `.dev.vars` for local secret management. * [**Static Assets**](https://developers.cloudflare.com/workers/wrangler/configuration/#assets) Static assets are always served from your local disk during development for speed and direct feedback on changes. * [**Version Metadata**](https://developers.cloudflare.com/workers/runtime-apis/bindings/version-metadata/): Since your Worker code is running locally, version metadata (like commit hash, version tags) associated with a specific deployed version is not applicable or accurate. * [**Analytics Engine**](https://developers.cloudflare.com/analytics/analytics-engine/): Local development sessions typically don't contribute data directly to production Analytics Engine. * [**Hyperdrive**](https://developers.cloudflare.com/workers/wrangler/configuration/#hyperdrive): This is being actively worked on, but is currently unsupported. * [**Rate Limiting**](https://developers.cloudflare.com/workers/runtime-apis/bindings/rate-limit/#configuration): Local development sessions typically should not share or affect rate limits of your deployed Workers. Rate limiting logic should be tested against local simulations. Note If you have use-cases for connecting to any of the remote resources above, please [open a feature request ↗](https://github.com/cloudflare/workers-sdk/issues) in our [workers-sdk repository ↗](https://github.com/cloudflare/workers-sdk). #### Using remote resources with Durable Objects and Workflows While Durable Object and Workflow bindings cannot currently be remote, you can still use them during local development and have them interact with remote resources. There are two recommended patterns for this: * **Local Durable Objects/Workflows with remote bindings:** When you enable remote bindings in your [Wrangler configuration](https://developers.cloudflare.com/workers/wrangler/configuration), your locally running Durable Objects and Workflows can access remote resources. This allows such bindings, although run locally, to interact with remote resources during local development. * **Accessing remote Durable Objects/Workflows via service bindings:** To interact with remote Durable Object or Workflow instances, deploy a Worker that defines those. Then, in your local Worker, configure a remote [service binding](https://developers.cloudflare.com/workers/runtime-apis/bindings/service-bindings/) pointing to the deployed Worker. Your local Worker will be then able to interact with the remote deployed Worker, which in turn can communicate with the remote Durable Objects/Workflows. Using this method, you can create a communication channel via the remote service binding, effectively using the deployed Worker as a proxy interface to the remote bindings during local development. ### Important Considerations * **Data modification**: Operations (writes, deletes, updates) on bindings connected remotely will affect your actual data in the targeted Cloudflare resource (be it preview or production). * **Billing**: Interactions with remote Cloudflare services through these connections will incur standard operational costs for those services (such as KV operations, R2 storage/operations, AI requests, D1 usage). * **Network latency**: Expect network latency for operations on these remotely connected bindings, as they involve communication over the internet. * **CI and non-interactive environments**: If your worker uses [Cloudflare Access](https://developers.cloudflare.com/cloudflare-one/), Wrangler must authenticate with Access when connecting to remote bindings. In non-interactive environments such as CI/CD pipelines, set the `CLOUDFLARE_ACCESS_CLIENT_ID` and `CLOUDFLARE_ACCESS_CLIENT_SECRET` [system environment variables](https://developers.cloudflare.com/workers/wrangler/system-environment-variables/) to authenticate using an [Access Service Token](https://developers.cloudflare.com/cloudflare-one/access-controls/service-credentials/service-tokens/). Without these variables, Wrangler throws an error instead of launching the interactive `cloudflared access login` flow. ### API Wrangler provides programmatic utilities to help tooling authors support remote binding connections when running Workers code with [Miniflare](https://developers.cloudflare.com/workers/testing/miniflare/). **Key APIs include:** * [startRemoteProxySession](#startRemoteProxySession): Starts a proxy session that allows interaction with remote bindings. * [unstable\_convertConfigBindingsToStartWorkerBindings](#unstable%5Fconvertconfigbindingstostartworkerbindings): Utility for converting binding definitions. * [experimental\_maybeStartOrUpdateProxySession](#experimental%5Fmaybestartorupdatemixedmodesession): Convenience function to easily start or update a proxy session. #### `startRemoteProxySession` This function starts a proxy session for a given set of bindings. It accepts options to control session behavior, including an `auth` option with your Cloudflare account ID and API token for remote binding access. It returns an object with: * `ready` ` Promise `: Resolves when the session is ready. * `dispose` ` () => Promise `: Stops the session. * `updateBindings` ` (bindings: StartDevWorkerInput['bindings']) => Promise `: Updates session bindings. * `remoteProxyConnectionString` ` remoteProxyConnectionString `: String to pass to Miniflare for remote binding access. #### `unstable_convertConfigBindingsToStartWorkerBindings` The `unstable_readConfig` utility returns an `Unstable_Config` object which includes the definition of the bindings included in the configuration file. These bindings definitions are however not directly compatible with `startRemoteProxySession`. It can be quite convenient to however read the binding declarations with `unstable_readConfig` and then pass them to `startRemoteProxySession`, so for this wrangler exposes `unstable_convertConfigBindingsToStartWorkerBindings` which is a simple utility to convert the bindings in an `Unstable_Config` object into a structure that can be passed to `startRemoteProxySession`. Note This type conversion is temporary. In the future, the types will be unified so you can pass the config object directly to `startRemoteProxySession`. #### `maybeStartOrUpdateRemoteProxySession` This wrapper simplifies proxy session management. It takes: * An object that contains either: * the path to a Wrangler configuration and a potential target environment * the name of the Worker and the bindings it is using * The current proxy session details (this parameter can be set to `null` or not being provided if none). * Potentially the auth data to use for the remote proxy session. It returns an object with the proxy session details if started or updated, or `null` if no proxy session is needed. The function: * Based on the first argument prepares the input arguments for the proxy session. * If there are no remote bindings to be used (nor a pre-existing proxy session) it returns null, signaling that no proxy session is needed. * If the details of an existing proxy session have been provided it updates the proxy session accordingly. * Otherwise if starts a new proxy session. * Returns the proxy session details (that can later be passed as the second argument to `maybeStartOrUpdateRemoteProxySession`). #### Example Here's a basic example of using Miniflare with `maybeStartOrUpdateRemoteProxySession` to provide a local dev session with remote bindings. This example uses a single hardcoded KV binding. * [ JavaScript ](#tab-panel-7343) * [ TypeScript ](#tab-panel-7344) JavaScript ``` import { Miniflare, MiniflareOptions } from "miniflare"; import { maybeStartOrUpdateRemoteProxySession } from "wrangler"; let mf; let remoteProxySessionDetails = null; async function startOrUpdateDevSession() { remoteProxySessionDetails = await maybeStartOrUpdateRemoteProxySession( { bindings: { MY_KV: { type: "kv_namespace", id: "kv-id", remote: true, }, }, }, remoteProxySessionDetails, ); const miniflareOptions = { scriptPath: "./worker.js", kvNamespaces: { MY_KV: { id: "kv-id", remoteProxyConnectionString: remoteProxySessionDetails?.session.remoteProxyConnectionString, }, }, }; if (!mf) { mf = new Miniflare(miniflareOptions); } else { mf.setOptions(miniflareOptions); } } // ... tool logic that invokes `startOrUpdateDevSession()` ... // ... once the dev session is no longer needed run // `remoteProxySessionDetails?.session.dispose()` ``` Explain Code TypeScript ``` import { Miniflare, MiniflareOptions } from "miniflare"; import { maybeStartOrUpdateRemoteProxySession } from "wrangler"; let mf: Miniflare | null; let remoteProxySessionDetails: Awaited< ReturnType > | null = null; async function startOrUpdateDevSession() { remoteProxySessionDetails = await maybeStartOrUpdateRemoteProxySession( { bindings: { MY_KV: { type: "kv_namespace", id: "kv-id", remote: true, }, }, }, remoteProxySessionDetails, ); const miniflareOptions: MiniflareOptions = { scriptPath: "./worker.js", kvNamespaces: { MY_KV: { id: "kv-id", remoteProxyConnectionString: remoteProxySessionDetails?.session.remoteProxyConnectionString, }, }, }; if (!mf) { mf = new Miniflare(miniflareOptions); } else { mf.setOptions(miniflareOptions); } } // ... tool logic that invokes `startOrUpdateDevSession()` ... // ... once the dev session is no longer needed run // `remoteProxySessionDetails?.session.dispose()` ``` Explain Code ## `wrangler dev --remote` (Legacy) Separate from Miniflare-powered local development, Wrangler also offers a fully remote development mode via [wrangler dev --remote](https://developers.cloudflare.com/workers/wrangler/commands/general/#dev). Remote development is [**not** supported in the Vite plugin](https://developers.cloudflare.com/workers/development-testing/wrangler-vs-vite/). npm yarn pnpm ``` npx wrangler dev --remote ``` ``` yarn wrangler dev --remote ``` ``` pnpm wrangler dev --remote ``` During **remote development**, all of your Worker code is uploaded to a temporary preview environment on Cloudflare's infrastructure, and changes to your code are automatically uploaded as you save. When using remote development, all bindings automatically connect to their remote resources. Unlike local development, you cannot configure bindings to use local simulations - they will always use the deployed resources on Cloudflare's network. ### When to use Remote development * For most development tasks, the most efficient and productive experience will be local development along with [remote bindings](https://developers.cloudflare.com/workers/development-testing/#remote-bindings) when needed. * You may want to use `wrangler dev --remote` for testing features or behaviors that are highly specific to Cloudflare's network and cannot be adequately simulated locally or tested via remote bindings. ### Considerations * Iteration is significantly slower than local development due to the upload/deployment step for each change. ### Limitations * When you run a remote development session using the `--remote` flag, a limit of 50 [routes](https://developers.cloudflare.com/workers/configuration/routing/routes/) per zone is enforced. Learn more in[ Workers platform limits](https://developers.cloudflare.com/workers/platform/limits/#routes-and-domains-when-using-wrangler-dev---remote). ```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/development-testing/","name":"Development & testing"}}]} ``` --- --- title: Supported bindings per development mode description: Supported bindings per development mode image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/development-testing/bindings-per-env.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Supported bindings per development mode ## Local development **Local simulations**: During local development, your Worker code always executes locally and bindings connect to locally simulated resources [by default](https://developers.cloudflare.com/workers/development-testing/#remote-bindings). This is supported in [wrangler dev](https://developers.cloudflare.com/workers/wrangler/commands/general/#dev) and the [Cloudflare Vite plugin](https://developers.cloudflare.com/workers/vite-plugin/). **Remote binding connections:**: Allows you to connect to remote resources on a [per-binding basis](https://developers.cloudflare.com/workers/development-testing/#remote-bindings). This is supported in [wrangler dev](https://developers.cloudflare.com/workers/wrangler/commands/general/#dev) and the [Cloudflare Vite plugin](https://developers.cloudflare.com/workers/vite-plugin/). | Binding | Local simulations | Remote binding connections | | --------------------------------------- | ----------------- | -------------------------- | | **AI** | ❌ | ✅ | | **Assets** | ✅ | ❌ | | **Analytics Engine** | ✅ | ❌ | | **Browser Rendering** | ✅ | ✅ | | **D1** | ✅ | ✅ | | **Durable Objects** | ✅ | ❌ [1](#user-content-fn-1) | | **Containers** | ✅ | ❌ | | **Email Bindings** | ✅ | ✅ | | **Hyperdrive** | ✅ | ❌ | | **Images** | ✅ | ✅ | | **KV** | ✅ | ✅ | | **Media Transformations** | ❌ | ✅ | | **mTLS** | ❌ | ✅ | | **Queues** | ✅ | ✅ | | **R2** | ✅ | ✅ | | **Rate Limiting** | ✅ | ❌ | | **Service Bindings (multiple Workers)** | ✅ | ✅ | | **Vectorize** | ❌ | ✅ | | **Workflows** | ✅ | ❌ | ## Remote development During remote development, all of your Worker code is uploaded and executed on Cloudflare's infrastructure, and bindings always connect to remote resources. **We recommend using local development with remote binding connections instead** for faster iteration and debugging. Supported only in [wrangler dev --remote](https://developers.cloudflare.com/workers/wrangler/commands/general/#dev) \- there is **no Vite plugin equivalent**. | Binding | Remote development | | --------------------------------------- | ------------------ | | **AI** | ✅ | | **Assets** | ✅ | | **Analytics Engine** | ✅ | | **Browser Rendering** | ✅ | | **D1** | ✅ | | **Durable Objects** | ✅ | | **Containers** | ❌ | | **Email Bindings** | ✅ | | **Hyperdrive** | ✅ | | **Images** | ✅ | | **KV** | ✅ | | **Media Transformations** | ✅ | | **mTLS** | ✅ | | **Queues** | ❌ | | **R2** | ✅ | | **Rate Limiting** | ✅ | | **Service Bindings (multiple Workers)** | ✅ | | **Vectorize** | ✅ | | **Workflows** | ❌ | ## Footnotes 1. Refer to [Using remote resources with Durable Objects and Workflows](https://developers.cloudflare.com/workers/development-testing/#using-remote-resources-with-durable-objects-and-workflows) for recommended workarounds. [↩](#user-content-fnref-1) ```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/development-testing/","name":"Development & testing"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/development-testing/bindings-per-env/","name":"Supported bindings per development mode"}}]} ``` --- --- title: Environment variables and secrets description: Configuring environment variables and secrets for local development image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/development-testing/environment-variables.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Environment variables and secrets Warning Do not use `vars` to store sensitive information in your Worker's Wrangler configuration file. Use secrets instead. Put secrets for use in local development in either a `.dev.vars` file or a `.env` file, in the same directory as the Wrangler configuration file. Note You can use the [secrets configuration property](https://developers.cloudflare.com/workers/wrangler/configuration/#secrets-configuration-property) to declare which secret names your Worker requires. When defined, only the keys listed in `secrets.required` are loaded from `.dev.vars` or `.env`. Additional keys are excluded and missing keys produce a warning. Choose to use either `.dev.vars` or `.env` but not both. If you define a `.dev.vars` file, then values in `.env` files will not be included in the `env` object during local development. These files should be formatted using the [dotenv ↗](https://hexdocs.pm/dotenvy/dotenv-file-format.html) syntax. For example: .dev.vars / .env ``` SECRET_KEY="value" API_TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" ``` Do not commit secrets to git The `.dev.vars` and `.env` files should not committed to git. Add `.dev.vars*` and `.env*` to your project's `.gitignore` file. To set different secrets for each Cloudflare environment, create files named `.dev.vars.` or `.env.`. When you select a Cloudflare environment in your local development, the corresponding environment-specific file will be loaded ahead of the generic `.dev.vars` (or `.env`) file. * When using `.dev.vars.` files, all secrets must be defined per environment. If `.dev.vars.` exists then only this will be loaded; the `.dev.vars` file will not be loaded. * In contrast, all matching `.env` files are loaded and the values are merged. For each variable, the value from the most specific file is used, with the following precedence: * `.env..local` (most specific) * `.env.local` * `.env.` * `.env` (least specific) Controlling `.env` handling It is possible to control how `.env` files are loaded in local development by setting environment variables on the process running the tools. * To disable loading local dev vars from `.env` files without providing a `.dev.vars` file, set the `CLOUDFLARE_LOAD_DEV_VARS_FROM_DOT_ENV` environment variable to `"false"`. * To include every environment variable defined in your system's process environment as a local development variable, ensure there is no `.dev.vars` and then set the `CLOUDFLARE_INCLUDE_PROCESS_ENV` environment variable to `"true"`. This is not needed when using the [secrets configuration property](https://developers.cloudflare.com/workers/wrangler/configuration/#secrets-configuration-property), which loads from `process.env` automatically. ### Basic setup Here are steps to set up environment variables for local development using either `.dev.vars` or `.env` files. 1. Create a `.dev.vars` / `.env` file in your project root. 2. Add key-value pairs: .dev.vars/.env ``` API_HOST="localhost:3000" DEBUG="true" SECRET_TOKEN="my-local-secret-token" ``` 3. Run your `dev` command **Wrangler** npm yarn pnpm ``` npx wrangler dev ``` ``` yarn wrangler dev ``` ``` pnpm wrangler dev ``` **Vite plugin** npm yarn pnpm ``` npx vite dev ``` ``` yarn vite dev ``` ``` pnpm vite dev ``` ## Multiple local environments To simulate different local environments, you can provide environment-specific files. For example, you might have a `staging` environment that requires different settings than your development environment. 1. Create a file named `.dev.vars.`/`.env.`. For example, we can use `.dev.vars.staging`/`.env.staging`. 2. Add key-value pairs: .dev.vars.staging/.env.staging ``` API_HOST="staging.localhost:3000" DEBUG="false" SECRET_TOKEN="staging-token" ``` 3. Specify the environment when running the `dev` command: **Wrangler** npm yarn pnpm ``` npx wrangler dev --env staging ``` ``` yarn wrangler dev --env staging ``` ``` pnpm wrangler dev --env staging ``` **Vite plugin** npm yarn pnpm ``` CLOUDFLARE_ENV=staging npx vite dev ``` ``` CLOUDFLARE_ENV=staging yarn vite dev ``` ``` CLOUDFLARE_ENV=staging pnpm vite dev ``` * If using `.dev.vars.staging`, only the values from that file will be applied instead of `.dev.vars`. * If using `.env.staging`, the values will be merged with `.env` files, with the most specific file taking precedence. ## Learn more * To learn how to configure multiple environments in Wrangler configuration, [read the documentation](https://developers.cloudflare.com/workers/wrangler/environments/#%5Ftop). * To learn how to use Wrangler environments and Vite environments together, [read the Vite plugin documentation](https://developers.cloudflare.com/workers/vite-plugin/reference/cloudflare-environments/) ```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/development-testing/","name":"Development & testing"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/development-testing/environment-variables/","name":"Environment variables and secrets"}}]} ``` --- --- title: Adding local data description: Populating local resources with data image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/development-testing/local-data.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Adding local data Whether you are using Wrangler or the [Cloudflare Vite plugin ↗](https://developers.cloudflare.com/workers/vite-plugin/), your workflow for **accessing** data during local development remains the same. However, you can only [populate local resources with data](https://developers.cloudflare.com/workers/development-testing/local-data/#populating-local-resources-with-data) via the Wrangler CLI. ### How it works When you run either `wrangler dev` or [vite ↗](https://vite.dev/guide/cli#dev-server), [Miniflare](https://developers.cloudflare.com/workers/testing/miniflare/) automatically creates **local versions** of your resources (like [KV](https://developers.cloudflare.com/kv), [D1](https://developers.cloudflare.com/d1/), or [R2](https://developers.cloudflare.com/r2)). This means you **don’t** need to manually set up separate local instances for each service. However, newly created local resources **won’t** contain any data — you'll need to use Wrangler commands with the `--local` flag to populate them. Changes made to local resources won’t affect production data. ## Populating local resources with data When you first start developing, your local resources will be empty. You'll need to populate them with data using the Wrangler CLI. ### KV namespaces Syntax note Since version 3.60.0, Wrangler supports the `kv ...` syntax. If you are using versions below 3.60.0, the command follows the `kv:...` syntax. Learn more in the [Wrangler commands for KV page](https://developers.cloudflare.com/kv/reference/kv-commands/). #### [Add a single key-value pair](https://developers.cloudflare.com/workers/wrangler/commands/kv/#kv-key) npm yarn pnpm ``` npx wrangler kv key put --binding= --local ``` ``` yarn wrangler kv key put --binding= --local ``` ``` pnpm wrangler kv key put --binding= --local ``` #### [Bulk upload](https://developers.cloudflare.com/workers/wrangler/commands/kv/#kv-bulk) npm yarn pnpm ``` npx wrangler kv bulk put --binding= --local ``` ``` yarn wrangler kv bulk put --binding= --local ``` ``` pnpm wrangler kv bulk put --binding= --local ``` ### R2 buckets #### [Upload a file](https://developers.cloudflare.com/workers/wrangler/commands/r2/#r2-object) npm yarn pnpm ``` npx wrangler r2 object put / --file= --local ``` ``` yarn wrangler r2 object put / --file= --local ``` ``` pnpm wrangler r2 object put / --file= --local ``` You may also include [other metadata](https://developers.cloudflare.com/workers/wrangler/commands/r2/#r2-object-put). ### D1 databases #### [Execute a SQL statement](https://developers.cloudflare.com/workers/wrangler/commands/d1/#d1-execute) npm yarn pnpm ``` npx wrangler d1 execute --command="" --local ``` ``` yarn wrangler d1 execute --command="" --local ``` ``` pnpm wrangler d1 execute --command="" --local ``` #### [Execute a SQL file](https://developers.cloudflare.com/workers/wrangler/commands/d1/#d1-execute) npm yarn pnpm ``` npx wrangler d1 execute --file=./schema.sql --local ``` ``` yarn wrangler d1 execute --file=./schema.sql --local ``` ``` pnpm wrangler d1 execute --file=./schema.sql --local ``` ### Durable Objects For Durable Objects, unlike KV, D1, and R2, there are no CLI commands to populate them with local data. To add data to Durable Objects during local development, you must write application code that creates Durable Object instances and [calls methods on them that store state](https://developers.cloudflare.com/durable-objects/best-practices/access-durable-objects-storage/). This typically involves creating development endpoints or test routes that initialize your Durable Objects with the desired data. ## Where local data gets stored By default, both Wrangler and the Vite plugin store local binding data in the same location: the `.wrangler/state` folder in your project directory. This folder stores data in subdirectories for all local bindings: KV namespaces, R2 buckets, D1 databases, Durable Objects, etc. ### Clearing local storage You can delete the `.wrangler/state` folder at any time to reset your local environment, and Miniflare will recreate it the next time you run your `dev` command. You can also delete specific sub-folders within `.wrangler/state` for more targeted clean-up. ### Changing the local data directory If you prefer to specify a different directory for local storage, you can do so through the Wranlger CLI or in the Vite plugin's configuration. #### Using Wrangler Use the [\--persist-to](https://developers.cloudflare.com/workers/wrangler/commands/general/#dev) flag with `wrangler dev`. You need to specify this flag every time you run the `dev` command: npm yarn pnpm ``` npx wrangler dev --persist-to ``` ``` yarn wrangler dev --persist-to ``` ``` pnpm wrangler dev --persist-to ``` Note The local persistence folder (like `.wrangler/state` or any custom folder you set) should be added to your `.gitignore` to avoid committing local development data to version control. Using `--local` with `--persist-to` If you run `wrangler dev --persist-to ` to specify a custom location for local data, you must also include the same `--persist-to ` when running other Wrangler commands that modify local data (and be sure to include the `--local` flag). For example, to create a KV key named `test` with a value of `12345` in a local KV namespace, run: npm yarn pnpm ``` npx wrangler kv key put test 12345 --binding MY_KV_NAMESPACE --local --persist-to worker-local ``` ``` yarn wrangler kv key put test 12345 --binding MY_KV_NAMESPACE --local --persist-to worker-local ``` ``` pnpm wrangler kv key put test 12345 --binding MY_KV_NAMESPACE --local --persist-to worker-local ``` This command: * Sets the KV key `test` to `12345` in the binding `MY_KV_NAMESPACE` (defined in your [Wrangler configuration file](https://developers.cloudflare.com/workers/wrangler/configuration/)). * Uses `--persist-to worker-local` to ensure the data is created in the **worker-local** directory instead of the default `.wrangler/state`. * Adds the `--local` flag, indicating you want to modify local data. If `--persist-to` is not specified, Wrangler defaults to using `.wrangler/state` for local data. #### Using the Cloudflare Vite plugin To customize where the Vite plugin stores local data, configure the [persistState option](https://developers.cloudflare.com/workers/vite-plugin/reference/api/#interface-pluginconfig) in your Vite config file: vite.config.js ``` import { defineConfig } from "vite"; import { cloudflare } from "@cloudflare/vite-plugin"; export default defineConfig({ plugins: [ cloudflare({ persistState: { path: "./my-custom-directory" }, }), ], }); ``` Explain Code #### Sharing state between tools If you want Wrangler and the Vite plugin to share the same state, configure them to use the same persistence path. ```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/development-testing/","name":"Development & testing"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/development-testing/local-data/","name":"Adding local data"}}]} ``` --- --- title: Local Explorer description: Browse and edit local binding data from your browser during development. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/development-testing/local-explorer.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Local Explorer Local Explorer is a browser-based interface for viewing and editing the data in your local [bindings](https://developers.cloudflare.com/workers/runtime-apis/bindings/) during development. It is available at `/cdn-cgi/explorer` on your local development server. Instead of running CLI commands or writing throwaway code to inspect local state, you can open Local Explorer in your browser and work with your data directly. This is useful when you want to seed test data, verify what your Worker wrote, debug a workflow run, or run ad-hoc SQL queries against a local [D1](https://developers.cloudflare.com/d1/) database. Local Explorer works with both [Wrangler](https://developers.cloudflare.com/workers/wrangler/) and the [Cloudflare Vite plugin](https://developers.cloudflare.com/workers/vite-plugin/). ## Prerequisites * Wrangler 4.82 or later, or the latest [Cloudflare Vite plugin](https://developers.cloudflare.com/workers/vite-plugin/) ## Open Local Explorer 1. Start a local development session: npm yarn pnpm ``` npx wrangler dev ``` ``` yarn wrangler dev ``` ``` pnpm wrangler dev ``` 1. Press `e` in your terminal to open Local Explorer in your browser. You can also navigate directly to `/cdn-cgi/explorer` on your Worker's route and port. Local Explorer is available by default and detects the bindings defined in your [Wrangler configuration](https://developers.cloudflare.com/workers/wrangler/configuration/) automatically. ## Supported bindings Local Explorer supports the following binding types: | Binding | View | Edit | | -------------------------------------------------------------------------------------- | ---------------------------------------------- | ------------------------------------------- | | [KV](https://developers.cloudflare.com/kv/) | Browse keys, view values and metadata | Create, update, and delete key-value pairs | | [R2](https://developers.cloudflare.com/r2/) | List objects, view metadata | Upload and delete objects | | [D1](https://developers.cloudflare.com/d1/) | Browse tables and rows, run SQL queries | Insert, update, and delete rows through SQL | | [Durable Objects](https://developers.cloudflare.com/durable-objects/) (SQLite storage) | Browse SQLite tables and rows, run SQL queries | Insert, update, and delete rows through SQL | | [Workflows](https://developers.cloudflare.com/workflows/) | List instances, view status and step history | Trigger new runs, retry failed instances | ### D1 and Durable Objects SQL studio For [D1](https://developers.cloudflare.com/d1/) databases and [Durable Objects](https://developers.cloudflare.com/durable-objects/) that use the [SQLite storage API](https://developers.cloudflare.com/durable-objects/api/sqlite-storage-api/), Local Explorer includes a SQL studio. This is the same experience available in the Cloudflare dashboard for deployed D1 databases. It provides both a visual table browser with inline editing and a SQL query editor where you can run arbitrary queries. ## API Local Explorer exposes an API at `/cdn-cgi/explorer/api` that provides programmatic access to the same operations available in the browser interface. The API serves an [OpenAPI specification ↗](https://www.openapis.org/) that describes all available endpoints, parameters, and response formats. To retrieve the OpenAPI spec: Terminal window ``` curl http://localhost:8787/cdn-cgi/explorer/api ``` ### Use with AI coding agents The OpenAPI spec at `/cdn-cgi/explorer/api` allows AI coding agents and other tools to discover and interact with your local bindings programmatically. An agent can fetch the spec, understand what resources are available, and make API calls to read or modify local data without requiring manual setup. This can be useful as an alternative to the CLI when you want an agent to: * Populate test data in your local [KV](https://developers.cloudflare.com/kv/) namespaces or [D1](https://developers.cloudflare.com/d1/) databases * Inspect the state of a [Durable Object](https://developers.cloudflare.com/durable-objects/) during debugging * Trigger or retry a [Workflow](https://developers.cloudflare.com/workflows/) run with different input data * Upload test files to a local [R2](https://developers.cloudflare.com/r2/) bucket ```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/development-testing/","name":"Development & testing"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/development-testing/local-explorer/","name":"Local Explorer"}}]} ``` --- --- title: Developing with multiple Workers description: Learn how to develop with multiple Workers using different approaches and configurations. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/development-testing/multi-workers.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Developing with multiple Workers When building complex applications, you may want to run multiple Workers during development. This guide covers the different approaches for running multiple Workers locally and when to use each approach. ## Single dev command Note We recommend this approach as the default for most development workflows as it ensures the best compatibility with bindings. You can run multiple Workers in a single dev command by passing multiple configuration files to your dev server: **Using Wrangler** npm yarn pnpm ``` npx wrangler dev -c ./app/wrangler.jsonc -c ./api/wrangler.jsonc ``` ``` yarn wrangler dev -c ./app/wrangler.jsonc -c ./api/wrangler.jsonc ``` ``` pnpm wrangler dev -c ./app/wrangler.jsonc -c ./api/wrangler.jsonc ``` The first config (`./app/wrangler.jsonc`) is treated as the primary Worker, exposed at `http://localhost:8787`. Additional configs (e.g. `./api/wrangler.jsonc`) run as auxiliary Workers, available via service bindings or tail consumers from the primary Worker. **Using the Vite plugin** Configure `auxiliaryWorkers` in your Vite configuration: vite.config.js ``` import { defineConfig } from "vite"; import { cloudflare } from "@cloudflare/vite-plugin"; export default defineConfig({ plugins: [ cloudflare({ configPath: "./app/wrangler.jsonc", auxiliaryWorkers: [ { configPath: "./api/wrangler.jsonc", }, ], }), ], }); ``` Explain Code Then run: npm yarn pnpm ``` npx vite dev ``` ``` yarn vite dev ``` ``` pnpm vite dev ``` **Use this approach when:** * You want the simplest setup for development * Workers are part of the same application or codebase * You need to access a Durable Object namespace or Workflow from another Worker using `script_name`, or set up Queues where the producer and consumer Workers are separated. ## Multiple dev commands You can also run each Worker in separate dev commands, each with its own terminal and configuration. npm yarn pnpm ``` # Terminal 1 npx wrangler dev -c ./app/wrangler.jsonc ``` ``` # Terminal 1 yarn wrangler dev -c ./app/wrangler.jsonc ``` ``` # Terminal 1 pnpm wrangler dev -c ./app/wrangler.jsonc ``` npm yarn pnpm ``` # Terminal 2 npx wrangler dev -c ./api/wrangler.jsonc ``` ``` # Terminal 2 yarn wrangler dev -c ./api/wrangler.jsonc ``` ``` # Terminal 2 pnpm wrangler dev -c ./api/wrangler.jsonc ``` These Workers run in different dev commands but can still communicate with each other via service bindings or tail consumers **regardless of whether they are started with `wrangler dev` or `vite dev`**. Note You can also combine both approaches — for example, run a group of Workers together through `vite dev` using `auxiliaryWorkers`, while running another Worker separately with `wrangler dev`. This allows you to keep tightly coupled Workers running under a single dev command, while keeping independent or shared Workers in separate ones. **Use this approach when:** * You want each Worker to be accessible on its own local URL during development, since only the primary Worker is exposed when using a single dev command * Each Worker has its own build setup or tooling — for example, one uses Vite with custom plugins while another is a vanilla Wrangler project * You need the flexibility to run and develop Workers independently without restructuring your project or consolidating configs This setup is especially useful in larger projects where each team maintains a subset of Workers. Running everything in a single dev command might require significant restructuring or build integration that isn't always practical. ```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/development-testing/","name":"Development & testing"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/development-testing/multi-workers/","name":"Developing with multiple Workers"}}]} ``` --- --- title: Testing image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/development-testing/testing.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Testing ```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/development-testing/","name":"Development & testing"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/development-testing/testing/","name":"Testing"}}]} ``` --- --- title: Vite Plugin image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/development-testing/vite-plugin.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Vite Plugin ```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/development-testing/","name":"Development & testing"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/development-testing/vite-plugin/","name":"Vite Plugin"}}]} ``` --- --- title: Choosing between Wrangler & Vite description: Choosing between Wrangler and Vite for local development image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/development-testing/wrangler-vs-vite.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Choosing between Wrangler & Vite # When to use Wrangler vs Vite Deciding between Wrangler and the Cloudflare Vite plugin depends on your project's focus and development workflow. Here are some quick guidelines to help you choose: ## When to use Wrangler * **Backend & Workers-focused:**If you're primarily building APIs, serverless functions, or background tasks, use Wrangler. * **Remote development:**If your project needs the ability to run your worker remotely on Cloudflare's network, use Wrangler's `--remote` flag. * **Simple frontends:**If you have minimal frontend requirements and don’t need hot reloading or advanced bundling, Wrangler may be sufficient. ## When to use the Cloudflare Vite Plugin Use the [Vite plugin](https://developers.cloudflare.com/workers/vite-plugin/) for: * **Frontend-centric development:**If you already use Vite with modern frontend frameworks like React, Vue, Svelte, or Solid, the Vite plugin integrates into your development workflow. * **React Router v7:**If you are using [React Router v7 ↗](https://reactrouter.com/) (the successor to Remix), it is officially supported by the Vite plugin as a full-stack SSR framework. * **Rapid iteration (HMR):**If you need near-instant updates in the browser, the Vite plugin provides [Hot Module Replacement (HMR) ↗](https://vite.dev/guide/features.html#hot-module-replacement) during local development. * **Advanced optimizations:**If you require more advanced optimizations (code splitting, efficient bundling, CSS handling, build time transformations, etc.), Vite is a strong fit. * **Greater flexibility:**Due to Vite's advanced configuration options and large ecosystem of plugins, there is more flexibility to customize your development experience and build output. ```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/development-testing/","name":"Development & testing"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/development-testing/wrangler-vs-vite/","name":"Choosing between Wrangler & Vite"}}]} ``` --- --- title: Playground description: The quickest way to experiment with Cloudflare Workers is in the Playground. It does not require any setup or authentication. The Playground is a sandbox which gives you an instant way to preview and test a Worker directly in the browser. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/playground.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Playground Browser support The Cloudflare Workers Playground is currently only supported in Firefox and Chrome desktop browsers. In Safari, it will show a `PreviewRequestFailed` error message. The quickest way to experiment with Cloudflare Workers is in the [Playground ↗](https://workers.cloudflare.com/playground). It does not require any setup or authentication. The Playground is a sandbox which gives you an instant way to preview and test a Worker directly in the browser. The Playground uses the same editor as the authenticated experience. The Playground provides the ability to [share](#share) the code you write as well as [deploy](#deploy) it instantly to Cloudflare's global network. This way, you can try new things out and deploy them when you are ready. [ Launch the Playground ](https://workers.cloudflare.com/playground) ## Hello Cloudflare Workers When you arrive in the Playground, you will see this default code: JavaScript ``` import welcome from "welcome.html"; /** * @typedef {Object} Env */ export default { /** * @param {Request} request * @param {Env} env * @param {ExecutionContext} ctx * @returns {Response} */ fetch(request, env, ctx) { console.log("Hello Cloudflare Workers!"); return new Response(welcome, { headers: { "content-type": "text/html", }, }); }, }; ``` Explain Code This is an example of a multi-module Worker that is receiving a [request](https://developers.cloudflare.com/workers/runtime-apis/request/), logging a message to the console, and then returning a [response](https://developers.cloudflare.com/workers/runtime-apis/response/) body containing the content from `welcome.html`. Refer to the [Fetch handler documentation](https://developers.cloudflare.com/workers/runtime-apis/handlers/fetch/) to learn more. ## Use the Playground As you edit the default code, the Worker will auto-update such that the preview on the right shows your Worker running just as it would in a browser. If your Worker uses URL paths, you can enter those in the input field on the right to navigate to them. The Playground provides type-checking via JSDoc comments and [workers-types ↗](https://www.npmjs.com/package/@cloudflare/workers-types). The Playground also provides pretty error pages in the event of application errors. To test a raw HTTP request (for example, to test a `POST` request), go to the **HTTP** tab and select **Send**. You can add and edit headers via this panel, as well as edit the body of a request. ## Log viewer The Playground and the quick editor in the Workers dashboard include a lightweight log viewer at the bottom of the preview panel. The log viewer displays the output of any calls to `console.log` made during preview runs. The log viewer supports the following: * Logging primitive values, objects, and arrays. * Clearing the log output between runs. At this time, the log viewer does not support logging class instances or their properties (for example, `request.url`). If you need a more complete development experience with full debugging capabilities, you can use [Wrangler](https://developers.cloudflare.com/workers/wrangler/install-and-update/) locally. To clone an existing Worker from your dashboard for local development, sign up and use the [wrangler init --from-dash](https://developers.cloudflare.com/workers/wrangler/commands/general/#init) command once your worker is deployed. ## Share To share what you have created, select **Copy Link** in the top right of the screen. This will copy a unique URL to your clipboard that you can share with anyone. These links do not expire, so you can bookmark your creation and share it at any time. Users that open a shared link will see the Playground with the shared code and preview. ## Deploy You can deploy a Worker from the Playground. If you are already logged in, you can review the Worker before deploying. Otherwise, you will be taken through the first-time user onboarding flow before you can review and deploy. Once deployed, your Worker will get its own unique URL and be available almost instantly on Cloudflare's global network. From here, you can add [Custom Domains](https://developers.cloudflare.com/workers/configuration/routing/custom-domains/), [storage resources](https://developers.cloudflare.com/workers/platform/storage-options/), and more. ```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/playground/","name":"Playground"}}]} ``` --- --- title: Configuration description: Worker configuration is managed through a Wrangler configuration file, which defines your project settings, bindings, and deployment options. Wrangler is the command-line tool used to develop, test, and deploy Workers. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/configuration/index.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Configuration Worker configuration is managed through a [Wrangler configuration file](https://developers.cloudflare.com/workers/wrangler/configuration/), which defines your project settings, bindings, and deployment options. Wrangler is the command-line tool used to develop, test, and deploy Workers. For more information on Wrangler, refer to [Wrangler](https://developers.cloudflare.com/workers/wrangler/). * [ Bindings ](https://developers.cloudflare.com/workers/runtime-apis/bindings/) * [ Compatibility dates ](https://developers.cloudflare.com/workers/configuration/compatibility-dates/) * [ Compatibility flags ](https://developers.cloudflare.com/workers/configuration/compatibility-flags/) * [ Cron Triggers ](https://developers.cloudflare.com/workers/configuration/cron-triggers/) * [ Environment variables ](https://developers.cloudflare.com/workers/configuration/environment-variables/) * [ Integrations ](https://developers.cloudflare.com/workers/configuration/integrations/) * [ Multipart upload metadata ](https://developers.cloudflare.com/workers/configuration/multipart-upload-metadata/) * [ Page Rules ](https://developers.cloudflare.com/workers/configuration/workers-with-page-rules/) * [ Placement ](https://developers.cloudflare.com/workers/configuration/placement/) * [ Preview URLs ](https://developers.cloudflare.com/workers/configuration/previews/) * [ Routes and domains ](https://developers.cloudflare.com/workers/configuration/routing/) * [ Secrets ](https://developers.cloudflare.com/workers/configuration/secrets/) * [ Versions & Deployments ](https://developers.cloudflare.com/workers/configuration/versions-and-deployments/) * [ Workers Sites ](https://developers.cloudflare.com/workers/configuration/sites/) ```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/configuration/","name":"Configuration"}}]} ``` --- --- title: Bindings (env) description: Worker Bindings that allow for interaction with other Cloudflare Resources. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) ### Tags [ Bindings ](https://developers.cloudflare.com/search/?tags=Bindings) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/runtime-apis/bindings/index.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Bindings (env) Bindings allow your Worker to interact with resources on the Cloudflare Developer Platform. Bindings provide better performance and less restrictions when accessing resources from Workers than the [REST APIs](https://developers.cloudflare.com/api/) which are intended for non-Workers applications. The following bindings are available today: * [ AI ](https://developers.cloudflare.com/workers-ai/get-started/workers-wrangler/#2-connect-your-worker-to-workers-ai) * [ Analytics Engine ](https://developers.cloudflare.com/analytics/analytics-engine) * [ Assets ](https://developers.cloudflare.com/workers/static-assets/binding/) * [ Browser Rendering ](https://developers.cloudflare.com/browser-rendering) * [ D1 ](https://developers.cloudflare.com/d1/worker-api/) * [ Dispatcher (Workers for Platforms) ](https://developers.cloudflare.com/cloudflare-for-platforms/workers-for-platforms/configuration/dynamic-dispatch/) * [ Durable Objects ](https://developers.cloudflare.com/durable-objects/api/) * [ Dynamic Worker Loaders ](https://developers.cloudflare.com/workers/runtime-apis/bindings/worker-loader/) * [ Environment Variables ](https://developers.cloudflare.com/workers/configuration/environment-variables/) * [ Hyperdrive ](https://developers.cloudflare.com/hyperdrive) * [ Images ](https://developers.cloudflare.com/images/transform-images/bindings/) * [ KV ](https://developers.cloudflare.com/kv/api/) * [ Media Transformations ](https://developers.cloudflare.com/stream/transform-videos/bindings/) * [ mTLS ](https://developers.cloudflare.com/workers/runtime-apis/bindings/mtls/) * [ Queues ](https://developers.cloudflare.com/queues/configuration/javascript-apis/) * [ R2 ](https://developers.cloudflare.com/r2/api/workers/workers-api-reference/) * [ Rate Limiting ](https://developers.cloudflare.com/workers/runtime-apis/bindings/rate-limit/) * [ Secrets ](https://developers.cloudflare.com/workers/configuration/secrets/) * [ Secrets Store ](https://developers.cloudflare.com/secrets-store/integrations/workers/) * [ Service bindings ](https://developers.cloudflare.com/workers/runtime-apis/bindings/service-bindings/) * [ Vectorize ](https://developers.cloudflare.com/vectorize/reference/client-api/) * [ Version metadata ](https://developers.cloudflare.com/workers/runtime-apis/bindings/version-metadata/) * [ Workflows ](https://developers.cloudflare.com/workflows/) ## What is a binding? When you declare a binding on your Worker, you grant it a specific capability, such as being able to read and write files to an [R2](https://developers.cloudflare.com/r2/) bucket. For example: * [ wrangler.jsonc ](#tab-panel-7696) * [ wrangler.toml ](#tab-panel-7697) JSONC ``` { "main": "./src/index.js", "r2_buckets": [ { "binding": "MY_BUCKET", "bucket_name": "" } ] } ``` TOML ``` main = "./src/index.js" [[r2_buckets]] binding = "MY_BUCKET" bucket_name = "" ``` * [ JavaScript ](#tab-panel-7682) * [ Python ](#tab-panel-7683) JavaScript ``` export default { async fetch(request, env) { const url = new URL(request.url); const key = url.pathname.slice(1); await env.MY_BUCKET.put(key, request.body); return new Response(`Put ${key} successfully!`); }, }; ``` Python ``` from workers import WorkerEntrypoint, Response from urllib.parse import urlparse class Default(WorkerEntrypoint): async def fetch(self, request): url = urlparse(request.url) key = url.path.slice(1) await self.env.MY_BUCKET.put(key, request.body) return Response(f"Put {key} successfully!") ``` You can think of a binding as a permission and an API in one piece. With bindings, you never have to add secret keys or tokens to your Worker in order to access resources on your Cloudflare account — the permission is embedded within the API itself. The underlying secret is never exposed to your Worker's code, and therefore can't be accidentally leaked. ## Making changes to bindings When you deploy a change to your Worker, and only change its bindings (i.e. you don't change the Worker's code), Cloudflare may reuse existing isolates that are already running your Worker. This improves performance — you can change an environment variable or other binding without unnecessarily reloading your code. As a result, you must be careful when "polluting" global scope with derivatives of your bindings. Anything you create there might continue to exist despite making changes to any underlying bindings. Consider an external client instance which uses a secret API key accessed from `env`: if you put this client instance in global scope and then make changes to the secret, a client instance using the original value might continue to exist. The correct approach would be to create a new client instance for each request. The following is a good approach: TypeScript ``` export default { fetch(request, env) { let client = new Client(env.MY_SECRET); // `client` is guaranteed to be up-to-date with the latest value of `env.MY_SECRET` since a new instance is constructed with every incoming request // ... do things with `client` }, }; ``` Compared to this alternative, which might have surprising and unwanted behavior: TypeScript ``` let client = undefined; export default { fetch(request, env) { client ??= new Client(env.MY_SECRET); // `client` here might not be updated when `env.MY_SECRET` changes, since it may already exist in global scope // ... do things with `client` }, }; ``` If you have more advanced needs, explore the [AsyncLocalStorage API](https://developers.cloudflare.com/workers/runtime-apis/nodejs/asynclocalstorage/), which provides a mechanism for exposing values down to child execution handlers. ## How to access `env` Bindings are located on the `env` object, which can be accessed in several ways: * It is an argument to entrypoint handlers such as [fetch](https://developers.cloudflare.com/workers/runtime-apis/fetch/): JavaScript ``` export default { async fetch(request, env) { return new Response(`Hi, ${env.NAME}`); }, }; ``` * It is as class property on [WorkerEntrypoint](https://developers.cloudflare.com/workers/runtime-apis/bindings/service-bindings/rpc/#bindings-env),[DurableObject](https://developers.cloudflare.com/durable-objects/), and [Workflow](https://developers.cloudflare.com/workflows/): * [ JavaScript ](#tab-panel-7684) * [ Python ](#tab-panel-7685) JavaScript ``` export class MyDurableObject extends DurableObject { async sayHello() { return `Hi, ${this.env.NAME}!`; } } ``` Python ``` from workers import WorkerEntrypoint, Response class Default(WorkerEntrypoint): async def fetch(self, request): return Response(f"Hi {self.env.NAME}") ``` * It can be imported from `cloudflare:workers`: * [ JavaScript ](#tab-panel-7686) * [ Python ](#tab-panel-7687) JavaScript ``` import { env } from "cloudflare:workers"; console.log(`Hi, ${env.Name}`); ``` Python ``` from workers import import_from_javascript env = import_from_javascript("cloudflare:workers").env print(f"Hi, {env.NAME}") ``` ### Importing `env` as a global Importing `env` from `cloudflare:workers` is useful when you need to access a binding such as [secrets](https://developers.cloudflare.com/workers/configuration/secrets/) or [environment variables](https://developers.cloudflare.com/workers/configuration/environment-variables/)in top-level global scope. For example, to initialize an API client: * [ JavaScript ](#tab-panel-7688) * [ Python ](#tab-panel-7689) JavaScript ``` import { env } from "cloudflare:workers"; import ApiClient from "example-api-client"; // API_KEY and LOG_LEVEL now usable in top-level scope let apiClient = ApiClient.new({ apiKey: env.API_KEY }); const LOG_LEVEL = env.LOG_LEVEL || "info"; export default { fetch(req) { // you can use apiClient or LOG_LEVEL, configured before any request is handled }, }; ``` Explain Code Python ``` from workers import WorkerEntrypoint, env from example_api_client import ApiClient api_client = ApiClient(api_key=env.API_KEY) LOG_LEVEL = getattr(env, "LOG_LEVEL", "info") class Default(WorkerEntrypoint): async def fetch(self, request): # ... ``` Workers do not allow I/O from outside a request context. This means that even though `env` is accessible from the top-level scope, you will not be able to access every binding's methods. For instance, environment variables and secrets are accessible, and you are able to call `env.NAMESPACE.get` to get a [Durable Object stub](https://developers.cloudflare.com/durable-objects/api/stub/) in the top-level context. However, calling methods on the Durable Object stub, making [calls to a KV store](https://developers.cloudflare.com/kv/api/), and [calling to other Workers](https://developers.cloudflare.com/workers/runtime-apis/bindings/service-bindings) will not work. * [ JavaScript ](#tab-panel-7690) * [ Python ](#tab-panel-7691) JavaScript ``` import { env } from "cloudflare:workers"; // This would error! // env.KV.get('my-key') export default { async fetch(req) { // This works let myVal = await env.KV.get("my-key"); Response.new(myVal); }, }; ``` Explain Code Python ``` from workers import Response, WorkerEntrypoint, env # This would fail! # env.KV.get('my-key') class Default(WorkerEntrypoint): async def fetch(self, request): # This works mv_val = await env.KV.get("my-key") return Response(my_val) ``` Explain Code Additionally, importing `env` from `cloudflare:workers` lets you avoid passing `env`as an argument through many function calls if you need to access a binding from a deeply-nested function. This can be helpful in a complex codebase. * [ JavaScript ](#tab-panel-7692) * [ Python ](#tab-panel-7693) JavaScript ``` import { env } from "cloudflare:workers"; export default { fetch(req) { Response.new(sayHello()); }, }; // env is not an argument to sayHello... function sayHello() { let myName = getName(); return `Hello, ${myName}`; } // ...nor is it an argument to getName function getName() { return env.MY_NAME; } ``` Explain Code Python ``` from workers import Response, WorkerEntrypoint, env class Default(WorkerEntrypoint): def fetch(req): return Response(say_hello()) # env is not an argument to say_hello... def say_hello(): my_name = get_name() return f"Hello, {myName}" # ...nor is it an argument to getName def get_name(): return env.MY_NAME ``` Explain Code Note While using `env` from `cloudflare:workers` may be simpler to write than passing it through a series of function calls, passing `env` as an argument is a helpful pattern for dependency injection and testing. ### Overriding `env` values The `withEnv` function provides a mechanism for overriding values of `env`. Imagine a user has defined the [environment variable](https://developers.cloudflare.com/workers/configuration/environment-variables/)"NAME" to be "Alice" in their Wrangler configuration file and deployed a Worker. By default, logging`env.NAME` would print "Alice". Using the `withEnv` function, you can override the value of "NAME". * [ JavaScript ](#tab-panel-7694) * [ Python ](#tab-panel-7695) JavaScript ``` import { env, withEnv } from "cloudflare:workers"; function logName() { console.log(env.NAME); } export default { fetch(req) { // this will log "Alice" logName(); withEnv({ NAME: "Bob" }, () => { // this will log "Bob" logName(); }); // ...etc... }, }; ``` Explain Code Python ``` from workers import Response, WorkerEntrypoint, env, patch_env def log_name(): print(env.NAME) class Default(WorkerEntrypoint): async def fetch(req): # this will log "Alice" log_name() with patch_env(NAME="Bob"): # this will log "Bob" log_name() # ...etc... ``` Explain Code This can be useful when testing code that relies on an imported `env` object. ```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/runtime-apis/","name":"Runtime APIs"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/runtime-apis/bindings/","name":"Bindings (env)"}}]} ``` --- --- title: Compatibility dates description: Opt into a specific version of the Workers runtime for your Workers project. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/configuration/compatibility-dates.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Compatibility dates Cloudflare regularly updates the Workers runtime. These updates apply to all Workers globally and should never cause a Worker that is already deployed to stop functioning. Sometimes, though, some changes may be backwards-incompatible. In particular, there might be bugs in the runtime API that existing Workers may inadvertently depend upon. Cloudflare implements bug fixes that new Workers can opt into while existing Workers will continue to see the buggy behavior to prevent breaking deployed Workers. The compatibility date and flags are how you, as a developer, opt into these runtime changes. [Compatibility flags](https://developers.cloudflare.com/workers/configuration/compatibility-flags) will often have a date in which they are enabled by default, and so, by specifying a `compatibility_date` for your Worker, you can quickly enable all of these various compatibility flags up to, and including, that date. ## Setting compatibility date When you start your project, you should always set `compatibility_date` to the current date. You should occasionally update the `compatibility_date` field. When updating, you should refer to the [compatibility flags](https://developers.cloudflare.com/workers/configuration/compatibility-flags) page to find out what has changed, and you should be careful to test your Worker to see if the changes affect you, updating your code as necessary. The new compatibility date takes effect when you next run the [npx wrangler deploy](https://developers.cloudflare.com/workers/wrangler/commands/general/#deploy) command. There is no need to update your `compatibility_date` if you do not want to. The Workers runtime will support old compatibility dates forever. If, for some reason, Cloudflare finds it is necessary to make a change that will break live Workers, Cloudflare will actively contact affected developers. That said, Cloudflare aims to avoid this if at all possible. However, even though you do not need to update the `compatibility_date` field, it is a good practice to do so for two reasons: 1. Sometimes, new features can only be made available to Workers that have a current `compatibility_date`. To access the latest features, you need to stay up-to-date. 2. Generally, other than the [compatibility flags](https://developers.cloudflare.com/workers/configuration/compatibility-flags) page, the Workers documentation may only describe the current `compatibility_date`, omitting information about historical behavior. If your Worker uses an old `compatibility_date`, you will need to continuously refer to the compatibility flags page in order to check if any of the APIs you are using have changed. #### Via Wrangler The compatibility date can be set in a Worker's [Wrangler configuration file](https://developers.cloudflare.com/workers/wrangler/configuration/). * [ wrangler.jsonc ](#tab-panel-7216) * [ wrangler.toml ](#tab-panel-7217) JSONC ``` { // Opt into backwards-incompatible changes through April 5, 2022. "compatibility_date": "2022-04-05" } ``` TOML ``` compatibility_date = "2022-04-05" ``` #### Via the Cloudflare Dashboard When a Worker is created through the Cloudflare Dashboard, the compatibility date is automatically set to the current date. The compatibility date can be updated in the Workers settings on the [Cloudflare dashboard ↗](https://dash.cloudflare.com/). #### Via the Cloudflare API The compatibility date can be set when uploading a Worker using the [Workers Script API](https://developers.cloudflare.com/api/resources/workers/subresources/scripts/methods/update/) or [Workers Versions API](https://developers.cloudflare.com/api/resources/workers/subresources/scripts/subresources/versions/methods/create/) in the request body's `metadata` field. If a compatibility date is not specified on upload via the API, it defaults to the oldest compatibility date, before any flags took effect (2021-11-02). When creating new Workers, it is highly recommended to set the compatibility date to the current date when uploading via the API. ```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/configuration/","name":"Configuration"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/configuration/compatibility-dates/","name":"Compatibility dates"}}]} ``` --- --- title: Compatibility flags description: Opt into a specific features of the Workers runtime for your Workers project. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/configuration/compatibility-flags.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Compatibility flags Compatibility flags enable specific features. They can be useful if you want to help the Workers team test upcoming changes that are not yet enabled by default, or if you need to hold back a change that your code depends on but still want to apply other compatibility changes. Compatibility flags will often have a date in which they are enabled by default, and so, by specifying a [compatibility\_date](https://developers.cloudflare.com/workers/configuration/compatibility-dates) for your Worker, you can quickly enable all of these various compatibility flags up to, and including, that date. ## Setting compatibility flags You may provide a list of `compatibility_flags`, which enable or disable specific changes. #### Via Wrangler Compatibility flags can be set in a Worker's [Wrangler configuration file](https://developers.cloudflare.com/workers/wrangler/configuration/). This example enables the specific flag `formdata_parser_supports_files`, which is described [below](https://developers.cloudflare.com/workers/configuration/compatibility-flags/#formdata-parsing-supports-file). As of the specified date, `2021-09-14`, this particular flag was not yet enabled by default, but, by specifying it in `compatibility_flags`, we can enable it anyway. `compatibility_flags` can also be used to disable changes that became the default in the past. * [ wrangler.jsonc ](#tab-panel-7220) * [ wrangler.toml ](#tab-panel-7221) JSONC ``` { // Opt into backwards-incompatible changes through September 14, 2021. "compatibility_date": "2021-09-14", // Also opt into an upcoming fix to the FormData API. "compatibility_flags": [ "formdata_parser_supports_files" ] } ``` TOML ``` compatibility_date = "2021-09-14" compatibility_flags = [ "formdata_parser_supports_files" ] ``` #### Via the Cloudflare Dashboard Compatibility flags can be updated in the Workers settings on the [Cloudflare dashboard ↗](https://dash.cloudflare.com/). #### Via the Cloudflare API Compatibility flags can be set when uploading a Worker using the [Workers Script API](https://developers.cloudflare.com/api/resources/workers/subresources/scripts/methods/update/) or [Workers Versions API](https://developers.cloudflare.com/api/resources/workers/subresources/scripts/subresources/versions/methods/create/) in the request body's `metadata` field. ## Node.js compatibility flag Note [The nodejs\_compat flag](https://developers.cloudflare.com/workers/runtime-apis/nodejs/) also enables `nodejs_compat_v2` as long as your compatibility date is 2024-09-23 or later. The v2 flag improves runtime Node.js compatibility by bundling additional polyfills and globals into your Worker. However, this improvement increases bundle size. If your compatibility date is 2024-09-22 or before and you want to enable v2, add the `nodejs_compat_v2` in addition to the `nodejs_compat` flag. If your compatibility date is after 2024-09-23, but you want to disable v2 to avoid increasing your bundle size, add the `no_nodejs_compat_v2` in addition to the `nodejs_compat flag`. A [growing subset](https://developers.cloudflare.com/workers/runtime-apis/nodejs/) of Node.js APIs are available directly as [Runtime APIs](https://developers.cloudflare.com/workers/runtime-apis/nodejs), with no need to add polyfills to your own code. To enable these APIs in your Worker, add the `nodejs_compat` compatibility flag to your [Wrangler configuration file](https://developers.cloudflare.com/workers/wrangler/configuration/): To enable both built-in runtime APIs and polyfills for your Worker or Pages project, add the [nodejs\_compat](https://developers.cloudflare.com/workers/configuration/compatibility-flags/#nodejs-compatibility-flag) [compatibility flag](https://developers.cloudflare.com/workers/configuration/compatibility-flags/#nodejs-compatibility-flag) to your [Wrangler configuration file](https://developers.cloudflare.com/workers/wrangler/configuration/), and set your compatibility date to September 23rd, 2024 or later. This will enable [Node.js compatibility](https://developers.cloudflare.com/workers/runtime-apis/nodejs/) for your Workers project. * [ wrangler.jsonc ](#tab-panel-7224) * [ wrangler.toml ](#tab-panel-7225) JSONC ``` { "compatibility_flags": [ "nodejs_compat" ], // Set this to today's date "compatibility_date": "2026-04-14" } ``` TOML ``` compatibility_flags = [ "nodejs_compat" ] # Set this to today's date compatibility_date = "2026-04-14" ``` * [ wrangler.jsonc ](#tab-panel-7218) * [ wrangler.toml ](#tab-panel-7219) JSONC ``` { "compatibility_flags": [ "nodejs_compat" ] } ``` TOML ``` compatibility_flags = [ "nodejs_compat" ] ``` As additional Node.js APIs are added, they will be made available under the `nodejs_compat` compatibility flag. Unlike most other compatibility flags, we do not expect the `nodejs_compat` to become active by default at a future date. The Node.js `AsyncLocalStorage` API is a particularly useful feature for Workers. To enable only the `AsyncLocalStorage` API, use the `nodejs_als` compatibility flag. * [ wrangler.jsonc ](#tab-panel-7222) * [ wrangler.toml ](#tab-panel-7223) JSONC ``` { "compatibility_flags": [ "nodejs_als" ] } ``` TOML ``` compatibility_flags = [ "nodejs_als" ] ``` ## Flags history Newest flags are listed first. ### Use an isolated PID namespace for containers | **Default as of** | 2026-04-01 | | ------------------- | ------------------------------ | | **Flag to enable** | containers\_pid\_namespace | | **Flag to disable** | no\_containers\_pid\_namespace | When `containers_pid_namespace` is set, containers will use an isolated PID namespace. The `ENTRYPOINT` of your container will have PID 1. When unset, the container shares the PID namespace with the virtual machine (VM) containing the container. The `ENTRYPOINT` of your container will _not_ have PID 1 and other processes running on the VM (that are not part of your container) will be visible. ### WebSocket auto-reply to close | **Default as of** | 2026-04-07 | | ------------------- | ------------------------------------- | | **Flag to enable** | web\_socket\_auto\_reply\_to\_close | | **Flag to disable** | web\_socket\_manual\_reply\_to\_close | When a server sends a WebSocket Close frame, the Workers runtime now automatically sends a reciprocal Close frame and transitions `readyState` to `CLOSED` before firing the `close` event. This matches the [WebSocket spec ↗](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/close%5Fevent) and browser behavior. Previously, receiving a server-initiated Close frame left the WebSocket in `CLOSING` and required the application to call `close()` itself. With this flag active, you no longer need to call `close()` in your `close` event handler. The runtime handles the close handshake automatically. JavaScript ``` const [client, server] = Object.values(new WebSocketPair()); server.accept(); server.addEventListener("close", (event) => { // readyState is already CLOSED — no need to call server.close(). console.log(server.readyState); // WebSocket.CLOSED console.log(event.code); // 1000 console.log(event.wasClean); // true }, { once: true }); ``` If you do still call `close()` inside the handler, the call is silently ignored. This means existing code that manually replies to Close frames will not break when you update your compatibility date. The automatic close behavior can interfere with WebSocket proxying. When a Worker proxies between a client and a backend, the old behavior allowed the Worker to observe a backend Close frame without the runtime tearing down the connection, giving the Worker time to coordinate a clean close on the client side. To support this pattern, the `accept()` method now accepts an option `allowHalfOpen`. Call `ws.accept({ allowHalfOpen: true })` to restore the old half-open behavior regardless of the compatibility flag. JavaScript ``` const [client, server] = Object.values(new WebSocketPair()); // Opt into half-open mode for proxying server.accept({ allowHalfOpen: true }); server.addEventListener("close", (event) => { // With allowHalfOpen true, readyState is still CLOSING here, // giving you time to coordinate the close on the other side. console.log(server.readyState); // WebSocket.CLOSING // Manually close when ready. server.close(1000, "done"); }, { once: true }); ``` Explain Code Note that there is no corresponding option to the `WebSocket` constructor. WebSockets constructed with `new WebSocket` will always auto-reply to closes after this flag takes effect. WebSockets constructed this way are automatically "accepted", so there is no opportunity to pass the option to `accept()`. If you are creating a WebSocket with `new WebSocket`, but you need half-open behavior, you will need to switch to using `fetch()` instead. JavaScript ``` // This does not allow half-open: let ws = new WebSocket("wss://example.com"); // But you can do this instead: let resp = await fetch("https://example.com", { headers: { "Upgrade": "websocket" } }); if (!resp.webSocket) { throw new Error("WebSocket handshake not accepted"); } let ws = resp.webSocket; ws.accept({ allowHalfOpen: true }); ``` Explain Code For more information, refer to the [WebSocket API documentation](https://developers.cloudflare.com/workers/runtime-apis/websockets/). ### Durable Object `deleteAll()` deletes alarms | **Default as of** | 2026-02-24 | | ------------------- | ----------------------------- | | **Flag to enable** | delete\_all\_deletes\_alarm | | **Flag to disable** | delete\_all\_preserves\_alarm | With the `delete_all_deletes_alarm` flag set, calling `deleteAll()` on a Durable Object's storage will delete any active alarm in addition to all stored data. Previously, `deleteAll()` only deleted user-stored data, and alarms required a separate `deleteAlarm()` call to remove. This change applies to both KV-backed and SQLite-backed Durable Objects. ### Duplicate stubs in RPC params instead of transferring ownership | **Default as of** | 2026-01-20 | | ------------------- | ---------------------------- | | **Flag to enable** | rpc\_params\_dup\_stubs | | **Flag to disable** | rpc\_params\_transfer\_stubs | Changes the ownership semantics of RPC stubs embedded in the parameters of an RPC call, fixing compatibility issues with [Cap'n Web ↗](https://github.com/cloudflare/capnweb). When the [Workers RPC system](https://developers.cloudflare.com/workers/runtime-apis/rpc/) was first introduced, RPC stubs that were embedded in the params or return value of some other call had their ownership transferred. That is, the original stub was implicitly disposed, with a duplicate stub being delivered to the destination. This turns out to compose poorly with another rule: in the callee, any stubs received in the params of a call are automatically disposed when the call returns. These two rules combine to mean that if you proxy a call -- i.e. the implementation of an RPC just makes another RPC call passing along the same params -- then any stubs in the params get disposed twice. Worse, if the eventual recipient of the stub wants to keep a duplicate past the end of the call, this may not work because the copy of the stub in the proxy layer gets disposed anyway, breaking the connection. For this reason, the pure-JS implementation of Cap'n Web switched to saying that stubs in params do NOT transfer ownership -- they are simply duplicated. This compat flag fixes the Workers Runtime built-in RPC to match Cap'n Web behavior. One common use case that this fixes is clients that subscribe to callbacks from a Durable Object via Cap'n Web. In this use case, the client app passes a callback function over a Cap'n Web WebSocket to a stateless Worker, which in turn forwards the stub over Workers RPC to a Durable Object. The Durable Object stores a [dup()](https://developers.cloudflare.com/workers/runtime-apis/rpc/lifecycle/#the-dup-method) of the stub in order to call it back later to notify the client of events. Unfortunately, before this flag, this didn't work: as soon as the subscribe function itself returned, the Cap'n Web stub in the stateless worker would be disposed (because it was a parameter to a call that returned, and it was not `dup()`ed within the context of the stateless worker). Hence, when the Durable Object later tried to call the subscription callback, it would receive "Error: RPC stub used after being disposed", despite the fact that it had carefully `dup()`ed the stub at its end. ### Enable ctx.exports | **Default as of** | 2025-11-17 | | ------------------- | --------------------- | | **Flag to enable** | enable\_ctx\_exports | | **Flag to disable** | disable\_ctx\_exports | This flag enables [the ctx.exports API](https://developers.cloudflare.com/workers/runtime-apis/context/#exports), which contains automatically-configured loopback bindings for your Worker's top-level exports. This allows you to skip configuring explicit bindings for your `WorkerEntrypoint`s and Durable Object namespaces defined in the same Worker. ### Automatic tracing | **Flag to enable** | enable\_workers\_observability\_tracing | | ------------------ | --------------------------------------- | This flag will enable [Workers Tracing](https://developers.cloudflare.com/workers/observability/traces/) by default if you have the following configured in your Wrangler configuration file: ``` { "observability": { "enabled": true } } ``` You can also explictly turn on automatic tracing without the flag and with older compatibility dates by setting the following: ``` { "observability": { "traces": { "enabled": true } } } ``` ### Enable `process` v2 implementation | **Default as of** | 2025-09-15 | | ------------------- | ---------------------------- | | **Flag to enable** | enable\_nodejs\_process\_v2 | | **Flag to disable** | disable\_nodejs\_process\_v2 | When enabled after 2025-09-15, the `enable_nodejs_process_v2` flag along with the [nodejs\_compat](https://developers.cloudflare.com/workers/runtime-apis/nodejs/) compat flag ensures a comprehensive Node.js-compatible `process` implementation, updating from the previous minimal process implementation that only provided the limited `nextTick`, `env`, `exit`, `getBuiltinModule`, `platform` and `features` properties. To continue using the previous minimal implementation after the compat date, set the `disable_nodejs_process_v2` flag instead. Most Node.js-supported process properties are implemented where possible, with undefined exports for unsupported features. See the [process documentation](https://developers.cloudflare.com/workers/runtime-apis/nodejs/process/) for Workers-specific implementation details. ### Enable Node.js HTTP server modules | **Default as of** | 2025-09-01 | | ------------------- | -------------------------------------- | | **Flag to enable** | enable\_nodejs\_http\_server\_modules | | **Flag to disable** | disable\_nodejs\_http\_server\_modules | The `enable_nodejs_http_server_modules` flag enables the availability of Node.js HTTP server modules such as `node:_http_server` in Workers. The `disable_nodejs_http_server_modules` flag disables the availability of these server modules. This enables compatibility with Node.js libraries and existing code that use the standard Node.js HTTP server APIs. The available functionality includes: * `http.createServer()` for creating HTTP servers * `http.Server` class for server instances * `http.ServerResponse` for handling server responses This flag must be used in combination with the `enable_nodejs_http_modules` flag to enable full features of `node:http`. This flag is automatically enabled for Workers using a compatibility date of 2025-09-01 or later when `nodejs_compat` is enabled. See the [Node.js documentation ↗](https://nodejs.org/docs/latest/api/http.html) for more details about the Node.js HTTP APIs. ### Enable availability of `node:http` and `node:https` modules | **Default as of** | 2025-08-15 | | ------------------- | ------------------------------ | | **Flag to enable** | enable\_nodejs\_http\_modules | | **Flag to disable** | disable\_nodejs\_http\_modules | The `enable_nodejs_http_modules` flag enables the availability of Node.js`node:http` and `node:https` modules in Workers (client APIS only). The `disable_nodejs_http_modules` flag disables the availability of these modules. This enables compatibility with Node.js libraries and existing code that use the standard node:http and node:https APIs for making HTTP requests. The available functionality includes: * `http.request()` and `https.request()` for making HTTP/HTTPS requests * `http.get()` and `https.get()` for making GET requests * Request and response objects with standard Node.js APIs * Support for standard HTTP methods, headers, and options See the [Node.js documentation ↗](https://nodejs.org/docs/latest/api/http.html)for more details about the Node.js APIs. ### Expose global MessageChannel and MessagePort | **Default as of** | 2025-08-15 | | ------------------- | ------------------------------------ | | **Flag to enable** | expose\_global\_message\_channel | | **Flag to disable** | no\_expose\_global\_message\_channel | When the `expose_global_message_channel` flag is set, Workers will expose the `MessageChannel` and `MessagePort` constructors globally. When the `no_expose_global_message_channel` flag is set, Workers will not expose these. ### Disable global handlers for Python Workers | **Default as of** | 2025-08-14 | | ------------------- | ------------------------------------- | | **Flag to enable** | python\_no\_global\_handlers | | **Flag to disable** | disable\_python\_no\_global\_handlers | When the `python_no_global_handlers` flag is set, Python Workers will disable the global handlers and enforce their use via default entrypoint classes. ### Enable `cache: no-cache` HTTP standard API | **Default as of** | 2025-08-07 | | ------------------- | -------------------------- | | **Flag to enable** | cache\_no\_cache\_enabled | | **Flag to disable** | cache\_no\_cache\_disabled | When you enable the `cache_no_cache_enabled` compatibility flag, you can specify the `no-cache`value for the `cache` property of the Request interface. When this compatibility flag is not enabled, or `cache_option_disabled` is set, the Workers runtime will throw a `TypeError` saying`Unsupported cache mode: no-cache`. When this flag is enabled you can instruct Cloudflare to force its cache to revalidate the response from a subrequest you make from your Worker using the [fetch()API](https://developers.cloudflare.com/workers/runtime-apis/fetch/): When `no-cache` is specified: * All requests have the headers `Pragma: no-cache` and `Cache-Control: no-cache` are set on them. * Subrequests to origins not hosted by Cloudflare force Cloudflare's cache to revalidate with the origin. Revalidating with the origin means that the Worker request will first look for a match in Cloudflare's cache, then: * If there is a match, a conditional request is sent to the origin, regardless of whether or not the match is fresh or stale. If the resource has not changed, the cached version is returned. If the resource has changed, it will be downloaded from the origin, updated in the cache, and returned. * If there is no match, Workers will make a standard request to the origin and cache the response. Examples using `cache: 'no-cache'`: JavaScript ``` const response = await fetch("https://example.com", { cache: "no-cache" }); ``` The cache value can also be set on a `Request` object. JavaScript ``` const request = new Request("https://example.com", { cache: "no-cache" }); const response = await fetch(request); ``` ### Set the `this` value of EventTarget event handlers | **Default as of** | 2025-08-01 | | ------------------- | ---------------------------- | | **Flag to enable** | set\_event\_target\_this | | **Flag to disable** | no\_set\_event\_target\_this | When the `set_event_target_this` flag is se, Workers will set the `this` value of event handlers to the `EventTarget` instance that the event is being dispatched on. This is compliant with the specification. When then `no_set_event_target_this` flag is set, Workers will not set the`this` value of event handlers, and it will be `undefined` instead. ### Set forwardable email full headers | **Default as of** | 2025-08-01 | | ------------------- | ---------------------------------------- | | **Flag to enable** | set\_forwardable\_email\_full\_headers | | **Flag to disable** | set\_forwardable\_email\_single\_headers | The original version of the headers sent to edgeworker were truncated to a single value for specific header names, such as To and Cc. With the`set_forwardable_email_full_headers` flag set, Workers will receive the full header values to the worker script. ### Pedantic Web Platform Tests (WPT) compliance | **Flag to enable** | pedantic\_wpt | | ------------------- | ------------------ | | **Flag to disable** | non\_pedantic\_wpt | The `pedantic_wpt` flag enables strict compliance with Web Platform Tests (WPT) in Workers. Initially this only effects `Event` and `EventTarget` APIs but will be expanded to other APIs in the future. There is no default enable date for this flag. ### Bind AsyncLocalStorage snapshots to the request | **Default as of** | 2025-06-16 | | ------------------- | ---------------------------------------------- | | **Flag to enable** | bind\_asynclocalstorage\_snapshot\_to\_request | | **Flag to disable** | do\_not\_bind\_asynclocalstorage\_snapshot\_to | The AsyncLocalStorage frame can capture values that are bound to the current request context. This is not always in the users control since we use the ALS storage frame to propagate internal trace spans as well as user-provided values. When the `bind_asynclocalstorage_snapshot_to_request`flag is set, the runtime binds the snapshot / bound functions to the current request context and will throw an error if the bound functions are called outside of the request in which they were created. The `do_not_bind_asynclocalstorage_snapshot_to` flag disables this behavior. ### Throw on unrecognized import assertions | **Default as of** | 2025-06-16 | | ------------------- | ------------------------------------------ | | **Flag to enable** | throw\_on\_unrecognized\_import\_assertion | | **Flag to disable** | ignore\_unrecognized\_import\_assertion | The `throw_on_unrecognized_import_assertion` flag controls how Workers handle import attributes that are not recognized by the runtime. Previously, Workers would ignore all import attributes, which is not compliant with the specification. Runtimes are expected to throw an error when an import attribute is encountered that is not recognized. When the `ignore_unrecognized_import_assertion` flag is set, Workers will ignore unrecognized import attributes. ### Enable eval during startup | **Default as of** | 2025-06-01 | | ------------------- | ------------------------------- | | **Flag to enable** | allow\_eval\_during\_startup | | **Flag to disable** | disallow\_eval\_during\_startup | When the `allow_eval_during_startup` flag is set, Workers can use `eval()`and `new Function(text)` during the startup phase of a Worker script. This allows for dynamic code execution at the beginning of a Worker lifecycle. When the `disallow_eval_during_startup` flag is set, using `eval()` or`new Function(text)` during the startup phase will throw an error. ### Enable `Request.signal` for incoming requests | **Flag to enable** | enable\_request\_signal | | ------------------- | ------------------------ | | **Flag to disable** | disable\_request\_signal | When you use the `enable_request_signal` compatibility flag, you can attach an event listener to [Request](https://developers.cloudflare.com/workers/runtime-apis/request/) objects, using the [signal property ↗](https://developer.mozilla.org/en-US/docs/Web/API/Request/signal). This allows you to perform tasks when the request to your Worker is canceled by the client. ### Enable `navigator.language` | **Default as of** | 2025-05-19 | | ------------------- | ---------------------------- | | **Flag to enable** | enable\_navigator\_language | | **Flag to disable** | disable\_navigator\_language | When the `enable_navigator_language` flag is set, the `navigator.language` property will be available in Workers. For now, the value of `navigator.language` will always be `en`. When the `disable_navigator_language` flag is set, the `navigator.language` property will not be available. ### Disallowing importable environment | **Flag to enable** | disallow\_importable\_env | | ------------------- | ------------------------- | | **Flag to disable** | allow\_importable\_env | When the `disallow_importable_env` flag is enabled, Workers will not allow importing the environment variables via the `cloudflare:workers` module and will not populate the environment variables in the global `process.env` object when Node.js compatibility is enabled. There is no default enabled date for this flag. ### Enable `FinalizationRegistry` and `WeakRef` | **Default as of** | 2025-05-05 | | ------------------- | ------------------ | | **Flag to enable** | enable\_weak\_ref | | **Flag to disable** | disable\_weak\_ref | Enables the use of [FinalizationRegistry ↗](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global%5FObjects/FinalizationRegistry) and [WeakRef ↗](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global%5FObjects/WeakRef) built-ins. * `FinalizationRegistry` allows you to register a cleanup callback that runs after an object has been garbage-collected. * `WeakRef` creates a weak reference to an object, allowing it to be garbage-collected if no other strong references exist. Behaviour `FinalizationRegistry` cleanup callbacks may execute at any point during your request lifecycle, even after your invoked handler has completed (similar to `ctx.waitUntil()`). These callbacks do not have an associated async context. You cannot perform any I/O within them, including emitting events to a tail Worker. These APIs are fundamentally non-deterministic. The timing and execution of garbage collection are unpredictable, and you **should not rely on them for essential program logic**. Additionally, cleanup callbacks registered with `FinalizationRegistry` may **never be executed**, including but not limited to cases where garbage collection is not triggered, or your Worker gets evicted. ### Passthrough AbortSignal of incoming request to subrequests | **Flag to enable** | request\_signal\_passthrough | | ------------------- | -------------------------------- | | **Flag to disable** | no\_request\_signal\_passthrough | When the `request_signal_passthrough` flag set, the `AbortSignal` of an incoming request will be passed through to subrequests when the request is forwarded to a subrequest using the `fetch()` API. The the `no_request_signal_passthrough` flag is set, the `AbortSignal` of the incoming request will not be passed through. ### Navigation requests prefer asset serving | **Default as of** | 2025-04-01 | | ------------------- | ------------------------------------------- | | **Flag to enable** | assets\_navigation\_prefers\_asset\_serving | | **Flag to disable** | assets\_navigation\_has\_no\_effect | For Workers with [static assets](https://developers.cloudflare.com/workers/static-assets/) and this compatibility flag enabled, navigation requests (requests which have a `Sec-Fetch-Mode: navigate` header) will prefer to be served by our asset-serving logic, even when an exact asset match cannot be found. This is particularly useful for applications which operate in either [Single Page Application (SPA) mode](https://developers.cloudflare.com/workers/static-assets/routing/single-page-application/) or have [custom 404 pages](https://developers.cloudflare.com/workers/static-assets/routing/static-site-generation/#custom-404-pages), as this now means the fallback pages of `200 /index.html` and `404 /404.html` will be served ahead of invoking a Worker script and will therefore avoid incurring a charge. Without this flag, the runtime will continue to apply the old behavior of invoking a Worker script (if present) for any requests which do not exactly match a static asset. When `assets.run_worker_first = true` is set, this compatibility flag has no effect. The `assets.run_worker_first = true` setting ensures the Worker script executes before any asset-serving logic. ### Enable auto-populating `process.env` | **Default as of** | 2025-04-01 | | ------------------- | ----------------------------------------------- | | **Flag to enable** | nodejs\_compat\_populate\_process\_env | | **Flag to disable** | nodejs\_compat\_do\_not\_populate\_process\_env | When you enable the `nodejs_compat_populate_process_env` compatibility flag and the [nodejs\_compat](https://developers.cloudflare.com/workers/runtime-apis/nodejs/)flag is also enabled, `process.env` will be populated with values from any bindings with text or JSON values. This means that if you have added [environment variables](https://developers.cloudflare.com/workers/configuration/environment-variables/),[secrets](https://developers.cloudflare.com/workers/configuration/secrets/), or [version metadata](https://developers.cloudflare.com/workers/runtime-apis/bindings/version-metadata/)bindings, these values can be accessed on `process.env`. JavaScript ``` const apiClient = ApiClient.new({ apiKey: process.env.API_KEY }); const LOG_LEVEL = process.env.LOG_LEVEL || "info"; ``` This makes accessing these values easier and conforms to common Node.js patterns, which can reduce toil and help with compatibility for existing Node.js libraries. If users do not wish for these values to be accessible via `process.env`, they can use the`nodejs_compat_do_not_populate_process_env` flag. In this case, `process.env` will still be available, but will not have values automatically added. If the `disallow_importable_env` compatibility flag is set, the `process.env` will also not be populated. ### Queue consumers don't wait for `ctx.waitUntil()` to resolve | **Flag to enable** | queue\_consumer\_no\_wait\_for\_wait\_until | | ------------------ | ------------------------------------------- | By default, [Queues](https://developers.cloudflare.com/queues/) Consumer Workers acknowledge messages only after promises passed to [ctx.waitUntil()](https://developers.cloudflare.com/workers/runtime-apis/context) have resolved. This behavior can cause queue consumers which utilize `ctx.waitUntil()` to process messages slowly. The default behavior is documented in the [Queues Consumer Configuration Guide](https://developers.cloudflare.com/queues/configuration/javascript-apis#consumer). This Consumer Worker is an example of a Worker which utilizes `ctx.waitUntil()`. Under the default behavior, this consumer Worker will only acknowledge a batch of messages after the sleep function has resolved. JavaScript ``` export default { async fetch(request, env, ctx) { // omitted }, async queue(batch, env, ctx) { console.log(`received batch of ${batch.messages.length} messages to queue ${batch.queue}`); for (let i = 0; i < batch.messages.length; ++i) { console.log(`message #${i}: ${JSON.stringify(batch.messages[i])}`); } ctx.waitUntil(sleep(30 * 1000)); } }; function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } ``` Explain Code If the `queue_consumer_no_wait_for_wait_until` flag is enabled, Queues consumers will no longer wait for promises passed to `ctx.waitUntil()` to resolve before acknowledging messages. This can improve the performance of queue consumers which utilize `ctx.waitUntil()`. With the flag enabled, in the above example, the consumer Worker will acknowledge the batch without waiting for the sleep function to resolve. Using this flag will not affect the behavior of `ctx.waitUntil()`. `ctx.waitUntil()` will continue to extend the lifetime of your consumer Worker to continue to work even after the batch of messages has been acknowledged. ### Apply TransformStream backpressure fix | **Default as of** | 2024-12-16 | | ------------------- | -------------------------------------- | | **Flag to enable** | fixup-transform-stream-backpressure | | **Flag to disable** | original-transform-stream-backpressure | The original implementation of `TransformStream` included a bug that would cause backpressure signaling to fail after the first write to the transform. Unfortunately, the fix can cause existing code written to address the bug to fail. Therefore, the `fixup-transform-stream-backpressure` compat flag is provided to enable the fix. The fix is enabled by default with compatibility dates of 2024-12-16 or later. To restore the original backpressure logic, disable the fix using the`original-transform-stream-backpressure` flag. ### Disable top-level await in require(...) | **Default as of** | 2024-12-02 | | ------------------- | --------------------------------------- | | **Flag to enable** | disable\_top\_level\_await\_in\_require | | **Flag to disable** | enable\_top\_level\_await\_in\_require | Workers implements the ability to use the Node.js style `require(...)` method to import modules in the Worker bundle. Historically, this mechanism allowed required modules to use top-level await. This, however, is not Node.js compatible. The `disable_top_level_await_in_require` compat flag will cause `require()`to fail if the module uses a top-level await. This flag is default enabled with a compatibility date of 2024-12-02 or later. To restore the original behavior allowing top-level await, use the`enable_top_level_await_in_require` compatibility flag. ### Enable `cache: no-store` HTTP standard API | **Default as of** | 2024-11-11 | | ------------------- | ----------------------- | | **Flag to enable** | cache\_option\_enabled | | **Flag to disable** | cache\_option\_disabled | When you enable the `cache_option_enabled` compatibility flag, you can specify a value for the `cache` property of the Request interface. When this compatibility flag is not enabled, or `cache_option_disabled` is set, the Workers runtime will throw an `Error` saying `The 'cache' field on 'RequestInitializerDict' is not implemented.` When this flag is enabled you can instruct Cloudflare not to cache the response from a subrequest you make from your Worker using the [fetch() API](https://developers.cloudflare.com/workers/runtime-apis/fetch/): The only cache option enabled with `cache_option_enabled` is `'no-store'`. Specifying any other value will cause the Workers runtime to throw a `TypeError` with the message `Unsupported cache mode: `. When `no-store` is specified: * All requests have the headers `Pragma: no-cache` and `Cache-Control: no-cache` are set on them. * Subrequests to origins not hosted by Cloudflare bypass Cloudflare's cache. Examples using `cache: 'no-store'`: JavaScript ``` const response = await fetch("https://example.com", { cache: "no-store" }); ``` The cache value can also be set on a `Request` object. JavaScript ``` const request = new Request("https://example.com", { cache: "no-store" }); const response = await fetch(request); ``` ### Global fetch() strictly public | **Flag to enable** | global\_fetch\_strictly\_public | | ------------------- | ------------------------------- | | **Flag to disable** | global\_fetch\_private\_origin | When the `global_fetch_strictly_public` compatibility flag is enabled, the global [fetch() function](https://developers.cloudflare.com/workers/runtime-apis/fetch/) will strictly route requests as if they were made on the public Internet. This means requests to a Worker's own zone will loop back to the "front door" of Cloudflare and will be treated like a request from the Internet, possibly even looping back to the same Worker again. When the `global_fetch_strictly_public` is not enabled, such requests are routed to the zone's origin server, ignoring any Workers mapped to the URL and also bypassing Cloudflare security settings. ### Upper-case HTTP methods | **Default as of** | 2024-10-14 | | ------------------- | ----------------------------------- | | **Flag to enable** | upper\_case\_all\_http\_methods | | **Flag to disable** | no\_upper\_case\_all\_http\_methods | HTTP methods are expected to be upper-cased. Per the fetch spec, if the method is specified as `get`, `post`, `put`, `delete`, `head`, or `options`, implementations are expected to uppercase the method. All other method names would generally be expected to throw as unrecognized (for example, `patch` would be an error while `PATCH` is accepted). This is a bit restrictive, even if it is in the spec. This flag modifies the behavior to uppercase all methods prior to parsing so that the method is always recognized if it is a known method. To restore the standard behavior, use the `no_upper_case_all_http_methods`compatibility flag. ### Automatically set the Symbol.toStringTag for Workers API objects | **Default as of** | 2024-09-26 | | ------------------- | --------------------------- | | **Flag to enable** | set\_tostring\_tag | | **Flag to disable** | do\_not\_set\_tostring\_tag | A change was made to set the Symbol.toStringTag on all Workers API objects in order to fix several spec compliance bugs. Unfortunately, this change was more breaking than anticipated. The `do_not_set_tostring_tag` compat flag restores the original behavior with compatibility dates of 2024-09-26 or earlier. ### Allow specifying a custom port when making a subrequest with the fetch() API | **Default as of** | 2024-09-02 | | ------------------- | --------------------- | | **Flag to enable** | allow\_custom\_ports | | **Flag to disable** | ignore\_custom\_ports | When this flag is enabled, and you specify a port when making a subrequest with the [fetch() API](https://developers.cloudflare.com/workers/runtime-apis/fetch/), the port number you specify will be used. When you make a subrequest to a website that uses Cloudflare ("Orange Clouded") — only [ports supported by Cloudflare's reverse proxy](https://developers.cloudflare.com/fundamentals/reference/network-ports/#network-ports-compatible-with-cloudflares-proxy) can be specified. If you attempt to specify an unsupported port, it will be ignored. When you make a subrequest to a website that does not use Cloudflare ("Grey Clouded") - any port can be specified. For example: JavaScript ``` const response = await fetch("https://example.com:8000"); ``` With allow\_custom\_ports the above example would fetch `https://example.com:8000` rather than`https://example.com:443`. Note that creating a WebSocket client with a call to `new WebSocket(url)` will also obey this flag. ### Properly extract blob MIME type from `content-type` headers | **Default as of** | 2024-06-03 | | ------------------- | -------------------------- | | **Flag to enable** | blob\_standard\_mime\_type | | **Flag to disable** | blob\_legacy\_mime\_type | When calling `response.blob.type()`, the MIME type will now be properly extracted from `content-type` headers, per the [WHATWG spec ↗](https://fetch.spec.whatwg.org/#concept-header-extract-mime-type). ### Use standard URL parsing in `fetch()` | **Default as of** | 2024-06-03 | | ------------------- | -------------------- | | **Flag to enable** | fetch\_standard\_url | | **Flag to disable** | fetch\_legacy\_url | The `fetch_standard_url` flag makes `fetch()` use [WHATWG URL Standard ↗](https://url.spec.whatwg.org/) parsing rules. The original implementation would throw `TypeError: Fetch API cannot load` errors with some URLs where standard parsing does not, for instance with the inclusion of whitespace before the URL. URL errors will now be thrown immediately upon calling `new Request()` with an improper URL. Previously, URL errors were thrown only once `fetch()` was called. ### Returning empty Uint8Array on final BYOB read | **Default as of** | 2024-05-13 | | ------------------- | ----------------------------------------- | | **Flag to enable** | internal\_stream\_byob\_return\_view | | **Flag to disable** | internal\_stream\_byob\_return\_undefined | In the original implementation of BYOB ("Bring your own buffer") `ReadableStreams`, the `read()` method would return `undefined` when the stream was closed and there was no more data to read. This behavior was inconsistent with the standard `ReadableStream` behavior, which returns an empty `Uint8Array` when the stream is closed. When the `internal_stream_byob_return_view` flag is used, the BYOB `read()` will implement standard behavior. JavaScript ``` const resp = await fetch('https://example.org'); const reader = resp.body.getReader({ mode: 'byob' }); await result = await reader.read(new Uint8Array(10)); if (result.done) { // The result gives us an empty Uint8Array... console.log(result.value.byteLength); // 0 // However, it is backed by the same underlying memory that was passed // into the read call. console.log(result.value.buffer.byteLength); // 10 } ``` Explain Code ### Brotli Content-Encoding support | **Default as of** | 2024-04-29 | | ------------------- | ----------------------------- | | **Flag to enable** | brotli\_content\_encoding | | **Flag to disable** | no\_brotli\_content\_encoding | When the `brotli_content_encoding` compatibility flag is enabled, Workers supports the `br` content encoding and can request and respond with data encoded using the [Brotli ↗](https://developer.mozilla.org/en-US/docs/Glossary/Brotli%5Fcompression) compression algorithm. This reduces the amount of data that needs to be fetched and can be used to pass through the original compressed data to the client. See the Fetch API [documentation](https://developers.cloudflare.com/workers/runtime-apis/fetch/#how-the-accept-encoding-header-is-handled) for details. ### Durable Object stubs and Service Bindings support RPC | **Default as of** | 2024-04-03 | | ------------------- | ---------- | | **Flag to enable** | rpc | | **Flag to disable** | no\_rpc | With this flag on, [Durable Object](https://developers.cloudflare.com/durable-objects/) stubs and [Service Bindings](https://developers.cloudflare.com/workers/runtime-apis/bindings/service-bindings/) support [RPC](https://developers.cloudflare.com/workers/runtime-apis/rpc/). This means that these objects now appear as if they define every possible method name. Calling any method name sends an RPC to the remote Durable Object or Worker service. For most applications, this change will have no impact unless you use it. However, it is possible some existing code will be impacted if it explicitly checks for the existence of method names that were previously not defined on these types. For example, we have seen code in the wild which iterates over [bindings](https://developers.cloudflare.com/workers/runtime-apis/bindings/) and tries to auto-detect their types based on what methods they implement. Such code will now see service bindings as implementing every method, so may misinterpret service bindings as being some other type. In the cases we have seen, the impact was benign (nothing actually broke), but out of caution we are guarding this change behind a flag. ### Handling custom thenables | **Default as of** | 2024-04-01 | | ------------------- | ----------------------------- | | **Flag to enable** | unwrap\_custom\_thenables | | **Flag to disable** | no\_unwrap\_custom\_thenables | With the `unwrap_custom_thenables` flag set, various Workers APIs that accept promises will also correctly handle custom thenables (objects with a `then` method) that are not native promises, but are intended to be treated as such). For example, the `waitUntil` method of the `ExecutionContext`object will correctly handle custom thenables, allowing them to be used in place of native promises. JavaScript ``` async fetch(req, env, ctx) { ctx.waitUntil({ then(res) { // Resolve the thenable after 1 second setTimeout(res, 1000); } }); // ... } ``` ### Fetchers no longer have get/put/delete helper methods | **Default as of** | 2024-03-26 | | ------------------- | ------------------------------ | | **Flag to enable** | fetcher\_no\_get\_put\_delete | | **Flag to disable** | fetcher\_has\_get\_put\_delete | [Durable Object](https://developers.cloudflare.com/durable-objects/) stubs and [Service Bindings](https://developers.cloudflare.com/workers/runtime-apis/bindings/service-bindings/) both implement a `fetch()` method which behaves similarly to the global `fetch()` method, but requests are instead sent to the destination represented by the object, rather than being routed based on the URL. Historically, API objects that had such a `fetch()` method also had methods `get()`, `put()`, and `delete()`. These methods were thin wrappers around `fetch()` which would perform the corresponding HTTP method and automatically handle writing/reading the request/response bodies as needed. These methods were a very early idea from many years ago, but were never actually documented, and therefore rarely (if ever) used. Enabling the `fetcher_no_get_put_delete`, or setting a compatibility date on or after `2024-03-26` disables these methods for your Worker. This change paves a future path for you to be able to define your own custom methods using these names. Without this change, you would be unable to define your own `get`, `put`, and `delete` methods, since they would conflict with these built-in helper methods. ### Queues send messages in `JSON` format | **Default as of** | 2024-03-18 | | ------------------- | -------------------------- | | **Flag to enable** | queues\_json\_messages | | **Flag to disable** | no\_queues\_json\_messages | With the `queues_json_messages` flag set, Queue bindings will serialize values passed to `send()` or `sendBatch()` into JSON format by default (when no specific `contentType` is provided). ### Suppress global `importScripts()` | **Default as of** | 2024-03-04 | | ------------------- | ------------------------- | | **Flag to enable** | no\_global\_importscripts | | **Flag to disable** | global\_importscripts | Suppresses the global `importScripts()` function. This method was included in the Workers global scope but was marked explicitly as non-implemented. However, the presence of the function could cause issues with some libraries. This compatibility flag removes the function from the global scope. ### Node.js AsyncLocalStorage | **Flag to enable** | nodejs\_als | | ------------------- | --------------- | | **Flag to disable** | no\_nodejs\_als | Enables the availability of the Node.js [AsyncLocalStorage ↗](https://nodejs.org/api/async%5Fhooks.html#async%5Fhooks%5Fclass%5Fasynclocalstorage) API in Workers. ### Python Workers | **Flag to enable** | python\_workers | | ------------------ | --------------- | This flag enables first class support for Python. [Python Workers](https://developers.cloudflare.com/workers/languages/python/) implement the majority of Python's [standard library](https://developers.cloudflare.com/workers/languages/python/stdlib), support all [bindings](https://developers.cloudflare.com/workers/runtime-apis/bindings), [environment variable](https://developers.cloudflare.com/workers/configuration/environment-variables), and [secrets](https://developers.cloudflare.com/workers/configuration/secrets), and integration with JavaScript objects and functions via a [foreign function interface](https://developers.cloudflare.com/workers/languages/python/ffi). ### WebCrypto preserve publicExponent field | **Default as of** | 2023-12-01 | | ------------------- | -------------------------------------- | | **Flag to enable** | crypto\_preserve\_public\_exponent | | **Flag to disable** | no\_crypto\_preserve\_public\_exponent | In the WebCrypto API, the `publicExponent` field of the algorithm of RSA keys would previously be an `ArrayBuffer`. Using this flag, `publicExponent` is a `Uint8Array` as mandated by the specification. ### `Vectorize` query with metadata optionally returned | **Default as of** | 2023-11-08 | | ------------------- | ------------------------------------ | | **Flag to enable** | vectorize\_query\_metadata\_optional | | **Flag to disable** | vectorize\_query\_original | A set value on `vectorize_query_metadata_optional` indicates that the Vectorize query operation should accept newer arguments with `returnValues` and `returnMetadata` specified discretely over the older argument `returnVectors`. This also changes the return format. If the vector values have been indicated for return, the return value is now a flattened vector object with `score` attached where it previously contained a nested vector object. ### WebSocket Compression | **Default as of** | 2023-08-15 | | ------------------- | ---------------------------- | | **Flag to enable** | web\_socket\_compression | | **Flag to disable** | no\_web\_socket\_compression | The Workers runtime did not support WebSocket compression when the initial WebSocket implementation was released. Historically, the runtime has stripped or ignored the `Sec-WebSocket-Extensions` header -- but is now capable of fully complying with the WebSocket Compression RFC. Since many clients are likely sending `Sec-WebSocket-Extensions: permessage-deflate` to their Workers today (`new WebSocket(url)` automatically sets this in browsers), we have decided to maintain prior behavior if this flag is absent. If the flag is present, the Workers runtime is capable of using WebSocket Compression on both inbound and outbound WebSocket connections. Like browsers, calling `new WebSocket(url)` in a Worker will automatically set the `Sec-WebSocket-Extensions: permessage-deflate` header. If you are using the non-standard `fetch()` API to obtain a WebSocket, you can include the `Sec-WebSocket-Extensions` header with value `permessage-deflate` and include any of the compression parameters defined in [RFC-7692 ↗](https://datatracker.ietf.org/doc/html/rfc7692#section-7). ### Strict crypto error checking | **Default as of** | 2023-08-01 | | ------------------- | -------------------------- | | **Flag to enable** | strict\_crypto\_checks | | **Flag to disable** | no\_strict\_crypto\_checks | Perform additional error checking in the Web Crypto API to conform with the specification and reject possibly unsafe key parameters: * For RSA key generation, key sizes are required to be multiples of 128 bits as boringssl may otherwise truncate the key. * The size of imported RSA keys must be at least 256 bits and at most 16384 bits, as with newly generated keys. * The public exponent for imported RSA keys is restricted to the commonly used values `[3, 17, 37, 65537]`. * In conformance with the specification, an error will be thrown when trying to import a public ECDH key with non-empty usages. ### Strict compression error checking | **Default as of** | 2023-08-01 | | ------------------- | ------------------------------- | | **Flag to enable** | strict\_compression\_checks | | **Flag to disable** | no\_strict\_compression\_checks | Perform additional error checking in the Compression Streams API and throw an error if a `DecompressionStream` has trailing data or gets closed before the full compressed data has been provided. ### Override cache rules cache settings in `request.cf` object for Fetch API | **Default as of** | 2025-04-02 | | ------------------- | ---------------------------------------- | | **Flag to enable** | request\_cf\_overrides\_cache\_rules | | **Flag to disable** | no\_request\_cf\_overrides\_cache\_rules | This flag changes the behavior of cache when requesting assets via the [Fetch API](https://developers.cloudflare.com/workers/runtime-apis/fetch). Cache settings specified in the `request.cf` object, such as `cacheEverything` and `cacheTtl`, are now given precedence over any [Cache Rules](https://developers.cloudflare.com/cache/how-to/cache-rules/) set. ### Bot Management data | **Default as of** | 2023-08-01 | | ------------------- | ------------------------------ | | **Flag to enable** | no\_cf\_botmanagement\_default | | **Flag to disable** | cf\_botmanagement\_default | This flag streamlines Workers requests by reducing unnecessary properties in the `request.cf` object. With the flag enabled - either by default after 2023-08-01 or by setting the `no_cf_botmanagement_default` flag - Cloudflare will only include the [Bot Management object](https://developers.cloudflare.com/bots/reference/bot-management-variables/) in a Worker's `request.cf` if the account has access to Bot Management. With the flag disabled, Cloudflare will include a default Bot Management object, regardless of whether the account is entitled to Bot Management. ### URLSearchParams delete() and has() value argument | **Default as of** | 2023-07-01 | | ------------------- | -------------------------------------------- | | **Flag to enable** | urlsearchparams\_delete\_has\_value\_arg | | **Flag to disable** | no\_urlsearchparams\_delete\_has\_value\_arg | The WHATWG introduced additional optional arguments to the `URLSearchParams` object [delete() ↗](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams/delete) and [has() ↗](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams/has) methods that allow for more precise control over the removal of query parameters. Because the arguments are optional and change the behavior of the methods when present there is a risk of breaking existing code. If your compatibility date is set to July 1, 2023 or after, this compatibility flag will be enabled by default. For an example of how this change could break existing code, consider code that uses the `Array` `forEach()` method to iterate through a number of parameters to delete: JavaScript ``` const usp = new URLSearchParams(); // ... ['abc', 'xyz'].forEach(usp.delete.bind(usp)); ``` The `forEach()` automatically passes multiple parameters to the function that is passed in. Prior to the addition of the new standard parameters, these extra arguments would have been ignored. Now, however, the additional arguments have meaning and change the behavior of the function. With this flag, the example above would need to be changed to: JavaScript ``` const usp = new URLSearchParams(); // ... ['abc', 'xyz'].forEach((key) => usp.delete(key)); ``` ### Use a spec compliant URL implementation in redirects | **Default as of** | 2023-03-14 | | ------------------- | --------------------------------- | | **Flag to enable** | response\_redirect\_url\_standard | | **Flag to disable** | response\_redirect\_url\_original | Change the URL implementation used in `Response.redirect()` to be spec-compliant (WHATWG URL Standard). ### Dynamic Dispatch Exception Propagation | **Default as of** | 2023-03-01 | | ------------------- | --------------------------------------------- | | **Flag to enable** | dynamic\_dispatch\_tunnel\_exceptions | | **Flag to disable** | dynamic\_dispatch\_treat\_exceptions\_as\_500 | Previously, when using Workers for Platforms' [dynamic dispatch API](https://developers.cloudflare.com/cloudflare-for-platforms/workers-for-platforms/configuration/dynamic-dispatch/) to send an HTTP request to a user Worker, if the user Worker threw an exception, the dynamic dispatch Worker would receive an HTTP `500` error with no body. When the `dynamic_dispatch_tunnel_exceptions` compatibility flag is enabled, the exception will instead propagate back to the dynamic dispatch Worker. The `fetch()` call in the dynamic dispatch Worker will throw the same exception. This matches the similar behavior of [service bindings](https://developers.cloudflare.com/workers/runtime-apis/bindings/service-bindings/) and [Durable Objects](https://developers.cloudflare.com/durable-objects/). ### `Headers` supports `getSetCookie()` | **Default as of** | 2023-03-01 | | ------------------- | ------------------------------- | | **Flag to enable** | http\_headers\_getsetcookie | | **Flag to disable** | no\_http\_headers\_getsetcookie | Adds the [getSetCookie() ↗](https://developer.mozilla.org/en-US/docs/Web/API/Headers/getSetCookie) method to the [Headers ↗](https://developer.mozilla.org/en-US/docs/Web/API/Headers) API in Workers. JavaScript ``` const response = await fetch("https://example.com"); let cookieValues = response.headers.getSetCookie(); ``` ### Node.js compatibility | **Flag to enable** | nodejs\_compat | | ------------------- | ------------------ | | **Flag to disable** | no\_nodejs\_compat | Enables [Node.js APIs](https://developers.cloudflare.com/workers/runtime-apis/nodejs/) in the Workers Runtime. Note that some Node.js APIs are only enabled if your Worker's compatibility date is set to on or after the following dates: | Node.js API | Enabled after | | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | | [http.server](https://developers.cloudflare.com/workers/configuration/compatibility-flags/#enable-nodejs-http-server-modules) | 2025-09-01 | | [node:http, node:https](https://developers.cloudflare.com/workers/configuration/compatibility-flags/#enable-availability-of-nodehttp-and-nodehttps-modules) | 2025-08-15 | | [process.env](https://developers.cloudflare.com/workers/configuration/compatibility-flags/#enable-auto-populating-processenv) | 2025-04-01 | | [Disable Top-level Await in require()](https://developers.cloudflare.com/workers/configuration/compatibility-flags/#disable-top-level-await-in-require) | 2024-12-02 | When enabling `nodejs_compat`, we recommend using the latest version of [Wrangler CLI](https://developers.cloudflare.com/workers/wrangler/), and the latest compatiblity date, in order to maximize compatibility. Some older versions of Wrangler inject additional polyfills that are no longer neccessary, as they are provided by the Workers runtime, if your Worker is using a more recent compatibility date. If you see errors using a particular NPM package on Workers, you should first try updating your compatibility date and use the latest version of [Wrangler CLI](https://developers.cloudflare.com/workers/wrangler/) or the [Cloudflare Vite Plugin](https://developers.cloudflare.com/workers/vite-plugin/). If you still encounter issues, please report them by [opening a GitHub issue ↗](https://github.com/cloudflare/workers-sdk/issues/new?template=bug-template.yaml). ### Streams Constructors | **Default as of** | 2022-11-30 | | ------------------- | ------------------------------ | | **Flag to enable** | streams\_enable\_constructors | | **Flag to disable** | streams\_disable\_constructors | Adds the work-in-progress `new ReadableStream()` and `new WritableStream()` constructors backed by JavaScript underlying sources and sinks. ### Compliant TransformStream constructor | **Default as of** | 2022-11-30 | | ------------------- | ----------------------------------------------- | | **Flag to enable** | transformstream\_enable\_standard\_constructor | | **Flag to disable** | transformstream\_disable\_standard\_constructor | Previously, the `new TransformStream()` constructor was not compliant with the Streams API standard. Use the `transformstream_enable_standard_constructor` to opt-in to the backwards-incompatible change to make the constructor compliant. Must be used in combination with the `streams_enable_constructors` flag. ### CommonJS modules do not export a module namespace | **Default as of** | 2022-10-31 | | ------------------- | --------------------------- | | **Flag to enable** | export\_commonjs\_default | | **Flag to disable** | export\_commonjs\_namespace | CommonJS modules were previously exporting a module namespace (an object like `{ default: module.exports }`) rather than exporting only the `module.exports`. When this flag is enabled, the export is fixed. ### Do not throw from async functions | **Default as of** | 2022-10-31 | | ------------------- | ------------------------------------ | | **Flag to enable** | capture\_async\_api\_throws | | **Flag to disable** | do\_not\_capture\_async\_api\_throws | The `capture_async_api_throws` compatibility flag will ensure that, in conformity with the standards API, async functions will only ever reject if they throw an error. The inverse `do_not_capture_async_api_throws` flag means that async functions which contain an error may throw that error synchronously rather than rejecting. ### New URL parser implementation | **Default as of** | 2022-10-31 | | ------------------- | ------------- | | **Flag to enable** | url\_standard | | **Flag to disable** | url\_original | The original implementation of the [URL ↗](https://developer.mozilla.org/en-US/docs/Web/API/URL) API in Workers was not fully compliant with the [WHATWG URL Standard ↗](https://url.spec.whatwg.org/), differing in several ways, including: * The original implementation collapsed sequences of multiple slashes into a single slash: `new URL("https://example.com/a//b").toString() === "https://example.com/a/b"` * The original implementation would throw `"TypeError: Invalid URL string."` if it encountered invalid percent-encoded escape sequences, like `https://example.com/a%%b`. * The original implementation would percent-encode or percent-decode certain content differently: `new URL("https://example.com/a%40b?c d%20e?f").toString() === "https://example.com/a@b?c+d+e%3Ff"` * The original implementation lacked more recently implemented `URL` features, like [URL.canParse() ↗](https://developer.mozilla.org/en-US/docs/Web/API/URL/canParse%5Fstatic). Set the compatibility date of your Worker to a date after `2022-10-31` or enable the `url_standard` compatibility flag to opt-in the fully spec compliant `URL` API implementation. Refer to the [response\_redirect\_url\_standard compatibility flag](https://developers.cloudflare.com/workers/configuration/compatibility-flags/#use-a-spec-compliant-url-implementation-in-redirects) , which affects the URL implementation used in `Response.redirect()`. ### `R2` bucket `list` respects the `include` option | **Default as of** | 2022-08-04 | | ------------------ | ------------------------ | | **Flag to enable** | r2\_list\_honor\_include | With the `r2_list_honor_include` flag set, the `include` argument to R2 `list` options is honored. With an older compatibility date and without this flag, the `include` argument behaves implicitly as `include: ["httpMetadata", "customMetadata"]`. ### Do not substitute `null` on `TypeError` | **Default as of** | 2022-06-01 | | ------------------- | --------------------------------------- | | **Flag to enable** | dont\_substitute\_null\_on\_type\_error | | **Flag to disable** | substitute\_null\_on\_type\_error | There was a bug in the runtime that meant that when being passed into built-in APIs, invalid values were sometimes mistakenly coalesced with `null`. Instead, a `TypeError` should have been thrown. The `dont_substitute_null_on_type_error` fixes this behavior so that an error is correctly thrown in these circumstances. ### Minimal subrequests | **Default as of** | 2022-04-05 | | ------------------- | ------------------------ | | **Flag to enable** | minimal\_subrequests | | **Flag to disable** | no\_minimal\_subrequests | With the `minimal_subrequests` flag set, `fetch()` subrequests sent to endpoints on the Worker's own zone (also called same-zone subrequests) have a reduced set of features applied to them. In general, these features should not have been initially applied to same-zone subrequests, and very few user-facing behavior changes are anticipated. Specifically, Workers might observe the following behavior changes with the new flag: * Response bodies will not be opportunistically gzipped before being transmitted to the Workers runtime. If a Worker reads the response body, it will read it in plaintext, as has always been the case, so disabling this prevents unnecessary decompression. Meanwhile, if the Worker passes the response through to the client, Cloudflare's HTTP proxy will opportunistically gzip the response body on that side of the Workers runtime instead. The behavior change observable by a Worker script should be that some `Content-Encoding: gzip` headers will no longer appear. * Automatic Platform Optimization may previously have been applied on both the Worker's initiating request and its subrequests in some circumstances. It will now only apply to the initiating request. * Link prefetching will now only apply to the Worker's response, not responses to the Worker's subrequests. ### Global `navigator` | **Default as of** | 2022-03-21 | | ------------------- | --------------------- | | **Flag to enable** | global\_navigator | | **Flag to disable** | no\_global\_navigator | With the `global_navigator` flag set, a new global `navigator` property is available from within Workers. Currently, it exposes only a single `navigator.userAgent` property whose value is set to `'Cloudflare-Workers'`. This property can be used to reliably determine whether code is running within the Workers environment. ### Do not use the Custom Origin Trust Store for external subrequests | **Default as of** | 2022-03-08 | | ------------------- | ----------------------------- | | **Flag to enable** | no\_cots\_on\_external\_fetch | | **Flag to disable** | cots\_on\_external\_fetch | The `no_cots_on_external_fetch` flag disables the use of the [Custom Origin Trust Store](https://developers.cloudflare.com/ssl/origin-configuration/custom-origin-trust-store/) when making external (grey-clouded) subrequests from a Cloudflare Worker. ### Setters/getters on API object prototypes | **Default as of** | 2022-01-31 | | ------------------- | --------------------------------------------- | | **Flag to enable** | workers\_api\_getters\_setters\_on\_prototype | | **Flag to disable** | workers\_api\_getters\_setters\_on\_instance | Originally, properties on Workers API objects were defined as instance properties as opposed to prototype properties. This broke subclassing at the JavaScript layer, preventing a subclass from correctly overriding the superclass getters/setters. This flag controls the breaking change made to set those getters/setters on the prototype template instead. This changes applies to: * `AbortSignal` * `AbortController` * `Blob` * `Body` * `DigestStream` * `Event` * `File` * `Request` * `ReadableStream` * `ReadableStreamDefaultReader` * `ReadableStreamBYOBReader` * `Response` * `TextDecoder` * `TextEncoder` * `TransformStream` * `URL` * `WebSocket` * `WritableStream` * `WritableStreamDefaultWriter` ### Durable Object `stub.fetch()` requires a full URL | **Default as of** | 2021-11-10 | | ------------------- | --------------------------------------------- | | **Flag to enable** | durable\_object\_fetch\_requires\_full\_url | | **Flag to disable** | durable\_object\_fetch\_allows\_relative\_url | Originally, when making a request to a Durable Object by calling `stub.fetch(url)`, a relative URL was accepted as an input. The URL would be interpreted relative to the placeholder URL `http://fake-host`, and the resulting absolute URL was delivered to the destination object's `fetch()` handler. This behavior was incorrect — full URLs were meant to be required. This flag makes full URLs required. ### `fetch()` improperly interprets unknown protocols as HTTP | **Default as of** | 2021-11-10 | | ------------------- | ------------------------------------------- | | **Flag to enable** | fetch\_refuses\_unknown\_protocols | | **Flag to disable** | fetch\_treats\_unknown\_protocols\_as\_http | Originally, if the `fetch()` function was passed a URL specifying any protocol other than `http:` or `https:`, it would silently treat it as if it were `http:`. For example, `fetch()` would appear to accept `ftp:` URLs, but it was actually making HTTP requests instead. Note that Cloudflare Workers supports a non-standard extension to `fetch()` to make it support WebSockets. However, when making an HTTP request that is intended to initiate a WebSocket handshake, you should still use `http:` or `https:` as the protocol, not `ws:` nor `wss:`. The `ws:` and `wss:` URL schemes are intended to be used together with the `new WebSocket()` constructor, which exclusively supports WebSocket. The extension to `fetch()` is designed to support HTTP and WebSocket in the same request (the response may or may not choose to initiate a WebSocket), and so all requests are considered to be HTTP. ### Streams BYOB reader detaches buffer | **Default as of** | 2021-11-10 | | ------------------- | ------------------------------------------------ | | **Flag to enable** | streams\_byob\_reader\_detaches\_buffer | | **Flag to disable** | streams\_byob\_reader\_does\_not\_detach\_buffer | Originally, the Workers runtime did not detach the `ArrayBuffer`s from user-provided TypedArrays when using the [BYOB reader's read() method](https://developers.cloudflare.com/workers/runtime-apis/streams/readablestreambyobreader/#methods), as required by the Streams spec, meaning it was possible to inadvertently reuse the same buffer for multiple `read()` calls. This change makes Workers conform to the spec. User code should never try to reuse an `ArrayBuffer` that has been passed into a [BYOB reader's read() method](https://developers.cloudflare.com/workers/runtime-apis/streams/readablestreambyobreader/#methods). Instead, user code can reuse the `ArrayBuffer` backing the result of the `read()` promise, as in the example below. JavaScript ``` // Consume and discard `readable` using a single 4KiB buffer. let reader = readable.getReader({ mode: "byob" }); let arrayBufferView = new Uint8Array(4096); while (true) { let result = await reader.read(arrayBufferView); if (result.done) break; // Optionally something with `result` here. // Re-use the same memory for the next `read()` by creating // a new Uint8Array backed by the result's ArrayBuffer. arrayBufferView = new Uint8Array(result.value.buffer); } ``` Explain Code The more recently added extension method `readAtLeast()` will always detach the `ArrayBuffer` and is unaffected by this feature flag setting. ### `FormData` parsing supports `File` | **Default as of** | 2021-11-03 | | ------------------- | ---------------------------------------------- | | **Flag to enable** | formdata\_parser\_supports\_files | | **Flag to disable** | formdata\_parser\_converts\_files\_to\_strings | [The FormData API ↗](https://developer.mozilla.org/en-US/docs/Web/API/FormData) is used to parse data (especially HTTP request bodies) in `multipart/form-data` format. Originally, the Workers runtime's implementation of the `FormData` API incorrectly converted uploaded files to strings. Therefore, `formData.get("filename")` would return a string containing the file contents instead of a `File` object. This change fixes the problem, causing files to be represented using `File` as specified in the standard. ### `HTMLRewriter` handling of `` | **Flag to enable** | html\_rewriter\_treats\_esi\_include\_as\_void\_tag | | ------------------ | --------------------------------------------------- | The HTML5 standard defines a fixed set of elements as void elements, meaning they do not use an end tag: ``, ``, `
`, ``, ``, ``, `
`, ``, ``, ``, ``, ``, ``, ``, ``, and ``. HTML5 does not recognize XML self-closing tag syntax. For example, `` ending tag is still required. The `/>` syntax simply is not recognized by HTML5 at all and it is treated the same as `>`. However, many developers still like to use this syntax, as a holdover from XHTML, a standard which failed to gain traction in the early 2000's. `` and `` are two tags that are not part of the HTML5 standard, but are instead used as part of [Edge Side Includes ↗](https://en.wikipedia.org/wiki/Edge%5FSide%5FIncludes), a technology for server-side HTML modification. These tags are not expected to contain any body and are commonly written with XML self-closing syntax. `HTMLRewriter` was designed to parse standard HTML5, not ESI. However, it would be useful to be able to implement some parts of ESI using `HTMLRewriter`. To that end, this compatibility flag causes `HTMLRewriter` to treat `` and `` as void tags, so that they can be parsed and handled properly. ## Experimental flags These flags can be enabled via `compatibility_flags`, but are not yet scheduled to become default on any particular date. ### Queue consumers don't wait for `ctx.waitUntil()` to resolve | **Flag to enable** | queue\_consumer\_no\_wait\_for\_wait\_until | | ------------------ | ------------------------------------------- | By default, [Queues](https://developers.cloudflare.com/queues/) Consumer Workers acknowledge messages only after promises passed to [ctx.waitUntil()](https://developers.cloudflare.com/workers/runtime-apis/context) have resolved. This behavior can cause queue consumers which utilize `ctx.waitUntil()` to process messages slowly. The default behavior is documented in the [Queues Consumer Configuration Guide](https://developers.cloudflare.com/queues/configuration/javascript-apis#consumer). This Consumer Worker is an example of a Worker which utilizes `ctx.waitUntil()`. Under the default behavior, this consumer Worker will only acknowledge a batch of messages after the sleep function has resolved. JavaScript ``` export default { async fetch(request, env, ctx) { // omitted }, async queue(batch, env, ctx) { console.log(`received batch of ${batch.messages.length} messages to queue ${batch.queue}`); for (let i = 0; i < batch.messages.length; ++i) { console.log(`message #${i}: ${JSON.stringify(batch.messages[i])}`); } ctx.waitUntil(sleep(30 * 1000)); } }; function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } ``` Explain Code If the `queue_consumer_no_wait_for_wait_until` flag is enabled, Queues consumers will no longer wait for promises passed to `ctx.waitUntil()` to resolve before acknowledging messages. This can improve the performance of queue consumers which utilize `ctx.waitUntil()`. With the flag enabled, in the above example, the consumer Worker will acknowledge the batch without waiting for the sleep function to resolve. Using this flag will not affect the behavior of `ctx.waitUntil()`. `ctx.waitUntil()` will continue to extend the lifetime of your consumer Worker to continue to work even after the batch of messages has been acknowledged. ### `HTMLRewriter` handling of `` | **Flag to enable** | html\_rewriter\_treats\_esi\_include\_as\_void\_tag | | ------------------ | --------------------------------------------------- | The HTML5 standard defines a fixed set of elements as void elements, meaning they do not use an end tag: ``, ``, `
`, ``, ``, ``, `
`, ``, ``, ``, ``, ``, ``, ``, ``, and ``. HTML5 does not recognize XML self-closing tag syntax. For example, `` ending tag is still required. The `/>` syntax simply is not recognized by HTML5 at all and it is treated the same as `>`. However, many developers still like to use this syntax, as a holdover from XHTML, a standard which failed to gain traction in the early 2000's. `` and `` are two tags that are not part of the HTML5 standard, but are instead used as part of [Edge Side Includes ↗](https://en.wikipedia.org/wiki/Edge%5FSide%5FIncludes), a technology for server-side HTML modification. These tags are not expected to contain any body and are commonly written with XML self-closing syntax. `HTMLRewriter` was designed to parse standard HTML5, not ESI. However, it would be useful to be able to implement some parts of ESI using `HTMLRewriter`. To that end, this compatibility flag causes `HTMLRewriter` to treat `` and `` as void tags, so that they can be parsed and handled properly. ```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/configuration/","name":"Configuration"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/configuration/compatibility-flags/","name":"Compatibility flags"}}]} ``` --- --- title: Cron Triggers description: Enable your Worker to be executed on a schedule. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/configuration/cron-triggers.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Cron Triggers ## Background Cron Triggers allow users to map a cron expression to a Worker using a [scheduled() handler](https://developers.cloudflare.com/workers/runtime-apis/handlers/scheduled/) that enables Workers to be executed on a schedule. Cron Triggers are ideal for running periodic jobs, such as for maintenance or calling third-party APIs to collect up-to-date data. Workers scheduled by Cron Triggers will run on underutilized machines to make the best use of Cloudflare's capacity and route traffic efficiently. Note Cron Triggers can also be combined with [Workflows](https://developers.cloudflare.com/workflows/) to trigger multi-step, long-running tasks. You can [bind to a Workflow](https://developers.cloudflare.com/workflows/build/workers-api/) directly from your Cron Trigger to execute a Workflow on a schedule. Cron Triggers execute on UTC time. ## Add a Cron Trigger ### 1\. Define a scheduled event listener To respond to a Cron Trigger, you must add a ["scheduled" handler](https://developers.cloudflare.com/workers/runtime-apis/handlers/scheduled/) to your Worker. * [ JavaScript ](#tab-panel-7226) * [ TypeScript ](#tab-panel-7227) * [ Python ](#tab-panel-7228) JavaScript ``` export default { async scheduled(controller, env, ctx) { console.log("cron processed"); }, }; ``` TypeScript ``` interface Env {} export default { async scheduled( controller: ScheduledController, env: Env, ctx: ExecutionContext, ) { console.log("cron processed"); }, }; ``` Explain Code Python ``` from workers import WorkerEntrypoint, Response class Default(WorkerEntrypoint): async def scheduled(self, controller, env, ctx): print("cron processed") ``` Refer to the following additional examples to write your code: * [Setting Cron Triggers](https://developers.cloudflare.com/workers/examples/cron-trigger/) * [Multiple Cron Triggers](https://developers.cloudflare.com/workers/examples/multiple-cron-triggers/) ### 2\. Update configuration Cron Trigger changes take time to propagate. Changes such as adding a new Cron Trigger, updating an old Cron Trigger, or deleting a Cron Trigger may take several minutes (up to 15 minutes) to propagate to the Cloudflare global network. After you have updated your Worker code to include a `"scheduled"` event, you must update your Worker project configuration. #### Via the [Wrangler configuration file](https://developers.cloudflare.com/workers/wrangler/configuration/) If a Worker is managed with Wrangler, Cron Triggers should be exclusively managed through the [Wrangler configuration file](https://developers.cloudflare.com/workers/wrangler/configuration/). Refer to the example below for a Cron Triggers configuration: * [ wrangler.jsonc ](#tab-panel-7231) * [ wrangler.toml ](#tab-panel-7232) JSONC ``` { "triggers": { // Schedule cron triggers: // - At every 3rd minute // - At 15:00 (UTC) on first day of the month // - At 23:59 (UTC) on the last weekday of the month "crons": [ "*/3 * * * *", "0 15 1 * *", "59 23 LW * *" ] } } ``` Explain Code TOML ``` [triggers] crons = [ "*/3 * * * *", "0 15 1 * *", "59 23 LW * *" ] ``` You also can set a different Cron Trigger for each [environment](https://developers.cloudflare.com/workers/wrangler/environments/) in your [Wrangler configuration file](https://developers.cloudflare.com/workers/wrangler/configuration/). You need to put the `triggers` array under your chosen environment. For example: * [ wrangler.jsonc ](#tab-panel-7233) * [ wrangler.toml ](#tab-panel-7234) JSONC ``` { "env": { "dev": { "triggers": { "crons": [ "0 * * * *" ] } } } } ``` Explain Code TOML ``` [env.dev.triggers] crons = [ "0 * * * *" ] ``` #### Via the dashboard To add Cron Triggers in the Cloudflare dashboard: 1. In the Cloudflare dashboard, go to the **Workers & Pages** page. [ Go to **Workers & Pages** ](https://dash.cloudflare.com/?to=/:account/workers-and-pages) 2. In **Overview**, select your Worker > **Settings** \> **Triggers** \> **Cron Triggers**. ## Supported cron expressions Cloudflare supports cron expressions with five fields, along with most [Quartz scheduler ↗](http://www.quartz-scheduler.org/documentation/quartz-2.3.0/tutorials/crontrigger.html#introduction)\-like cron syntax extensions: | Field | Values | Characters | | ------------- | ------------------------------------------------------------------ | ------------ | | Minute | 0-59 | \* , - / | | Hours | 0-23 | \* , - / | | Days of Month | 1-31 | \* , - / L W | | Months | 1-12, case-insensitive 3-letter abbreviations ("JAN", "aug", etc.) | \* , - / | | Weekdays | 1-7, case-insensitive 3-letter abbreviations ("MON", "fri", etc.) | \* , - / L # | Note Days of the week go from 1 = Sunday to 7 = Saturday, which is different on some other cron systems (where 0 = Sunday and 6 = Saturday). To avoid ambiguity you may prefer to use the three-letter abbreviations (e.g. `SUN` rather than 1). ### Examples Some common time intervals that may be useful for setting up your Cron Trigger: * `* * * * *` * At every minute * `*/30 * * * *` * At every 30th minute * `45 * * * *` * On the 45th minute of every hour * `0 17 * * sun` or `0 17 * * 1` * 17:00 (UTC) on Sunday * `10 7 * * mon-fri` or `10 7 * * 2-6` * 07:10 (UTC) on weekdays * `0 15 1 * *` * 15:00 (UTC) on first day of the month * `0 18 * * 6L` or `0 18 * * friL` * 18:00 (UTC) on the last Friday of the month * `59 23 LW * *` * 23:59 (UTC) on the last weekday of the month ## Test Cron Triggers locally Test Cron Triggers using Wrangler with [wrangler dev](https://developers.cloudflare.com/workers/wrangler/commands/general/#dev), or using the [Cloudflare Vite plugin ↗](https://developers.cloudflare.com/workers/vite-plugin/). This will expose a `/cdn-cgi/handler/scheduled` route which can be used to test using a HTTP request. Terminal window ``` curl "http://localhost:8787/cdn-cgi/handler/scheduled" ``` To simulate different cron patterns, a `cron` query parameter can be passed in. Terminal window ``` curl "http://localhost:8787/cdn-cgi/handler/scheduled?cron=*+*+*+*+*" ``` Optionally, you can also pass a `time` query parameter to override `controller.scheduledTime` in your scheduled event listener. Terminal window ``` curl "http://localhost:8787/cdn-cgi/handler/scheduled?cron=*+*+*+*+*&time=1745856238" ``` ## View past events To view the execution history of Cron Triggers, view **Cron Events**: 1. In the Cloudflare dashboard, go to the **Workers & Pages** page. [ Go to **Workers & Pages** ](https://dash.cloudflare.com/?to=/:account/workers-and-pages) 2. In **Overview**, select your **Worker**. 3. Select **Settings**. 4. Under **Trigger Events**, select **View events**. Cron Events stores the 100 most recent invocations of the Cron scheduled event. [Workers Logs](https://developers.cloudflare.com/workers/observability/logs/workers-logs) also records invocation logs for the Cron Trigger with a longer retention period and a filter & query interface. If you are interested in an API to access Cron Events, use Cloudflare's [GraphQL Analytics API](https://developers.cloudflare.com/analytics/graphql-api). Note It can take up to 30 minutes before events are displayed in **Past Cron Events** when creating a new Worker or changing a Worker's name. Refer to [Metrics and Analytics](https://developers.cloudflare.com/workers/observability/metrics-and-analytics/) for more information. ## Remove a Cron Trigger ### Via the dashboard To delete a Cron Trigger on a deployed Worker via the dashboard: 1. In the Cloudflare dashboard, go to the **Workers & Pages** page. [ Go to **Workers & Pages** ](https://dash.cloudflare.com/?to=/:account/workers-and-pages) 2. Select your Worker. 3. Go to **Triggers** \> select the three dot icon next to the Cron Trigger you want to remove > **Delete**. #### Via the [Wrangler configuration file](https://developers.cloudflare.com/workers/wrangler/configuration/) If a Worker is managed with Wrangler, Cron Triggers should be exclusively managed through the [Wrangler configuration file](https://developers.cloudflare.com/workers/wrangler/configuration/). When deploying a Worker with Wrangler any previous Cron Triggers are replaced with those specified in the `triggers` array. * If the `crons` property is an empty array then all the Cron Triggers are removed. * If the `triggers` or `crons` property are `undefined` then the currently deploy Cron Triggers are left in-place. * [ wrangler.jsonc ](#tab-panel-7229) * [ wrangler.toml ](#tab-panel-7230) JSONC ``` { "triggers": { // Remove all cron triggers: "crons": [] } } ``` TOML ``` [triggers] crons = [ ] ``` ## Limits Refer to [Limits](https://developers.cloudflare.com/workers/platform/limits/) to track the maximum number of Cron Triggers per Worker. ## Green Compute With Green Compute enabled, your Cron Triggers will only run on Cloudflare points of presence that are located in data centers that are powered purely by renewable energy. Organizations may claim that they are powered by 100 percent renewable energy if they have procured sufficient renewable energy to account for their overall energy use. Renewable energy can be purchased in a number of ways, including through on-site generation (wind turbines, solar panels), directly from renewable energy producers through contractual agreements called Power Purchase Agreements (PPA), or in the form of Renewable Energy Credits (REC, IRECs, GoOs) from an energy credit market. Green Compute can be configured at the account level: 1. In the Cloudflare dashboard, go to the **Workers & Pages** page. [ Go to **Workers & Pages** ](https://dash.cloudflare.com/?to=/:account/workers-and-pages) 2. In the **Account details** section, find **Compute Setting**. 3. Select **Change**. 4. Select **Green Compute**. 5. Select **Confirm**. ## Related resources * [Triggers](https://developers.cloudflare.com/workers/wrangler/configuration/#triggers) \- Review Wrangler configuration file syntax for Cron Triggers. * Learn how to access Cron Triggers in [ES modules syntax](https://developers.cloudflare.com/workers/reference/migrate-to-module-workers/) for an optimized experience. ```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/configuration/","name":"Configuration"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/configuration/cron-triggers/","name":"Cron Triggers"}}]} ``` --- --- title: Environment variables description: You can add environment variables, which are a type of binding, to attach text strings or JSON values to your Worker. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/configuration/environment-variables.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Environment variables ## Background You can add environment variables, which are a type of binding, to attach text strings or JSON values to your Worker. Environment variables are available on the [env parameter](https://developers.cloudflare.com/workers/runtime-apis/handlers/fetch/#parameters) passed to your Worker's [fetch event handler](https://developers.cloudflare.com/workers/runtime-apis/handlers/fetch/). Text strings and JSON values are not encrypted and are useful for storing application configuration. ## Add environment variables via Wrangler To add env variables using Wrangler, define text and JSON via the `[vars]` configuration in your Wrangler file. In the following example, `API_HOST` and `API_ACCOUNT_ID` are text values and `SERVICE_X_DATA` is a JSON value. * [ wrangler.jsonc ](#tab-panel-7241) * [ wrangler.toml ](#tab-panel-7242) JSONC ``` { "$schema": "./node_modules/wrangler/config-schema.json", "name": "my-worker-dev", "vars": { "API_HOST": "example.com", "API_ACCOUNT_ID": "example_user", "SERVICE_X_DATA": { "URL": "service-x-api.dev.example", "MY_ID": 123 } } } ``` Explain Code TOML ``` "$schema" = "./node_modules/wrangler/config-schema.json" name = "my-worker-dev" [vars] API_HOST = "example.com" API_ACCOUNT_ID = "example_user" [vars.SERVICE_X_DATA] URL = "service-x-api.dev.example" MY_ID = 123 ``` Explain Code Refer to the following example on how to access the `API_HOST` environment variable in your Worker code: * [ JavaScript ](#tab-panel-7235) * [ TypeScript ](#tab-panel-7236) JavaScript ``` export default { async fetch(request, env, ctx) { return new Response(`API host: ${env.API_HOST}`); }, }; ``` TypeScript ``` export interface Env { API_HOST: string; } export default { async fetch(request, env, ctx): Promise { return new Response(`API host: ${env.API_HOST}`); }, } satisfies ExportedHandler; ``` ### Import `env` for global access You can also import `env` from [cloudflare:workers](https://developers.cloudflare.com/workers/runtime-apis/bindings/#importing-env-as-a-global) to access environment variables from anywhere in your code, including outside of request handlers: * [ JavaScript ](#tab-panel-7239) * [ TypeScript ](#tab-panel-7240) JavaScript ``` import { env } from "cloudflare:workers"; // Access environment variables at the top level const apiHost = env.API_HOST; export default { async fetch(request) { return new Response(`API host: ${apiHost}`); }, }; ``` Explain Code TypeScript ``` import { env } from "cloudflare:workers"; // Access environment variables at the top level const apiHost = env.API_HOST; export default { async fetch(request: Request): Promise { return new Response(`API host: ${apiHost}`); }, }; ``` Explain Code This approach is useful when you need to: * Initialize configuration or API clients at the top level of your Worker. * Access environment variables from deeply nested functions without passing `env` through every function call. For more details, refer to [Importing env as a global](https://developers.cloudflare.com/workers/runtime-apis/bindings/#importing-env-as-a-global). ### Configuring different environments in Wrangler [Environments in Wrangler](https://developers.cloudflare.com/workers/wrangler/environments) let you specify different configurations for the same Worker, including different values for `vars` in each environment. As `vars` is a [non-inheritable key](https://developers.cloudflare.com/workers/wrangler/configuration/#non-inheritable-keys), they are not inherited by environments and must be specified for each environment. The example below sets up two environments, `staging` and `production`, with different values for `API_HOST`. * [ wrangler.jsonc ](#tab-panel-7237) * [ wrangler.toml ](#tab-panel-7238) JSONC ``` { "$schema": "./node_modules/wrangler/config-schema.json", "name": "my-worker-dev", // top level environment "vars": { "API_HOST": "api.example.com" }, "env": { "staging": { "vars": { "API_HOST": "staging.example.com" } }, "production": { "vars": { "API_HOST": "production.example.com" } } } } ``` Explain Code TOML ``` "$schema" = "./node_modules/wrangler/config-schema.json" name = "my-worker-dev" [vars] API_HOST = "api.example.com" [env.staging.vars] API_HOST = "staging.example.com" [env.production.vars] API_HOST = "production.example.com" ``` Explain Code To run Wrangler commands in specific environments, you can pass in the `--env` or `-e` flag. For example, you can develop the Worker in an environment called `staging` by running `npx wrangler dev --env staging`, and deploy it with `npx wrangler deploy --env staging`. Learn about [environments in Wrangler](https://developers.cloudflare.com/workers/wrangler/environments). ## Add environment variables via the dashboard To add environment variables via the dashboard: 1. In the Cloudflare dashboard, go to the **Workers & Pages** page. [ Go to **Workers & Pages** ](https://dash.cloudflare.com/?to=/:account/workers-and-pages) 2. In **Overview**, select your Worker. 3. Select **Settings**. 4. Under **Variables and Secrets**, select **Add**. 5. Select a **Type**, input a **Variable name**, and input its **Value**. This variable will be made available to your Worker. 6. (Optional) To add multiple environment variables, select **Add variable**. 7. Select **Deploy** to implement your changes. Plaintext strings and secrets Select the **Secret** type if your environment variable is a [secret](https://developers.cloudflare.com/workers/configuration/secrets/). Alternatively, consider [Cloudflare Secrets Store](https://developers.cloudflare.com/secrets-store/), for account-level secrets. ## Compare secrets and environment variables Use secrets for sensitive information Do not use plaintext environment variables to store sensitive information. Use [secrets](https://developers.cloudflare.com/workers/configuration/secrets/) or [Secrets Store bindings](https://developers.cloudflare.com/secrets-store/integrations/workers/) instead. [Secrets](https://developers.cloudflare.com/workers/configuration/secrets/) are [environment variables](https://developers.cloudflare.com/workers/configuration/environment-variables/). The difference is secret values are not visible within Wrangler or Cloudflare dashboard after you define them. This means that sensitive data, including passwords or API tokens, should always be encrypted to prevent data leaks. To your Worker, there is no difference between an environment variable and a secret. The secret's value is passed through as defined. ### Local development with secrets Warning Do not use `vars` to store sensitive information in your Worker's Wrangler configuration file. Use secrets instead. Put secrets for use in local development in either a `.dev.vars` file or a `.env` file, in the same directory as the Wrangler configuration file. Note You can use the [secrets configuration property](https://developers.cloudflare.com/workers/wrangler/configuration/#secrets-configuration-property) to declare which secret names your Worker requires. When defined, only the keys listed in `secrets.required` are loaded from `.dev.vars` or `.env`. Additional keys are excluded and missing keys produce a warning. Choose to use either `.dev.vars` or `.env` but not both. If you define a `.dev.vars` file, then values in `.env` files will not be included in the `env` object during local development. These files should be formatted using the [dotenv ↗](https://hexdocs.pm/dotenvy/dotenv-file-format.html) syntax. For example: .dev.vars / .env ``` SECRET_KEY="value" API_TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" ``` Do not commit secrets to git The `.dev.vars` and `.env` files should not committed to git. Add `.dev.vars*` and `.env*` to your project's `.gitignore` file. To set different secrets for each Cloudflare environment, create files named `.dev.vars.` or `.env.`. When you select a Cloudflare environment in your local development, the corresponding environment-specific file will be loaded ahead of the generic `.dev.vars` (or `.env`) file. * When using `.dev.vars.` files, all secrets must be defined per environment. If `.dev.vars.` exists then only this will be loaded; the `.dev.vars` file will not be loaded. * In contrast, all matching `.env` files are loaded and the values are merged. For each variable, the value from the most specific file is used, with the following precedence: * `.env..local` (most specific) * `.env.local` * `.env.` * `.env` (least specific) Controlling `.env` handling It is possible to control how `.env` files are loaded in local development by setting environment variables on the process running the tools. * To disable loading local dev vars from `.env` files without providing a `.dev.vars` file, set the `CLOUDFLARE_LOAD_DEV_VARS_FROM_DOT_ENV` environment variable to `"false"`. * To include every environment variable defined in your system's process environment as a local development variable, ensure there is no `.dev.vars` and then set the `CLOUDFLARE_INCLUDE_PROCESS_ENV` environment variable to `"true"`. This is not needed when using the [secrets configuration property](https://developers.cloudflare.com/workers/wrangler/configuration/#secrets-configuration-property), which loads from `process.env` automatically. ## Environment variables and Node.js compatibility When you enable [nodejs\_compat](https://developers.cloudflare.com/workers/runtime-apis/nodejs/) and the [nodejs\_compat\_populate\_process\_env](https://developers.cloudflare.com/workers/configuration/compatibility-flags/#nodejs%5Fcompat%5Fpopulate%5Fprocess%5Fenv) compatibility flag (enabled by default for compatibility dates on or after 2025-04-01), environment variables are available via the global `process.env`. The `process.env` will be populated lazily the first time that `process` is accessed in the worker. Text variable values are exposed directly. JSON variable values that evaluate to string values are exposed as the parsed value. JSON variable values that do not evaluate to string values are exposed as the raw JSON string. For example, imagine a Worker with three environment variables, two text values, and one JSON value: ``` [vars] FOO = "abc" BAR = "abc" BAZ = { "a": 123 } ``` Environment variables can be added using either the `wrangler.{json|jsonc|toml}` file or via the Cloudflare dashboard UI. The values of `process.env.FOO` and `process.env.BAR` will each be the JavaScript string `"abc"`. The value of `process.env.BAZ` will be the JSON-encoded string `"{ \"a\": 123 }"`. Note Note also that because secrets are a form of environment variable within the runtime, secrets are also exposed via `process.env`. ## Related resources * Migrating environment variables from [Service Worker format to ES modules syntax](https://developers.cloudflare.com/workers/reference/migrate-to-module-workers/#environment-variables). ```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/configuration/","name":"Configuration"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/configuration/environment-variables/","name":"Environment variables"}}]} ``` --- --- title: Integrations description: Integrate with third-party services and products. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/configuration/integrations/index.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Integrations One of the key features of Cloudflare Workers is the ability to integrate with other services and products. In this document, we will explain the types of integrations available with Cloudflare Workers and provide step-by-step instructions for using them. ## Types of integrations Cloudflare Workers offers several types of integrations, including: * [Databases](https://developers.cloudflare.com/workers/databases/): Cloudflare Workers can be integrated with a variety of databases, including SQL and NoSQL databases. This allows you to store and retrieve data from your databases directly from your Cloudflare Workers code. * [APIs](https://developers.cloudflare.com/workers/configuration/integrations/apis/): Cloudflare Workers can be used to integrate with external APIs, allowing you to access and use the data and functionality exposed by those APIs in your own code. * [Third-party services](https://developers.cloudflare.com/workers/configuration/integrations/external-services/): Cloudflare Workers can be used to integrate with a wide range of third-party services, such as payment gateways, authentication providers, and more. This makes it possible to use these services in your Cloudflare Workers code. ## How to use integrations To use any of the available integrations: * Determine which integration you want to use and make sure you have the necessary accounts and credentials for it. * In your Cloudflare Workers code, import the necessary libraries or modules for the integration. * Use the provided APIs and functions to connect to the integration and access its data or functionality. * Store necessary secrets and keys using secrets via [wrangler secret put ](https://developers.cloudflare.com/workers/wrangler/commands/general/#secret). ## Tips and best practices To help you get the most out of using integrations with Cloudflare Workers: * Secure your integrations and protect sensitive data. Ensure you use secure authentication and authorization where possible, and ensure the validity of libraries you import. * Use [caching](https://developers.cloudflare.com/workers/reference/how-the-cache-works) to improve performance and reduce the load on an external service. * Split your Workers into service-oriented architecture using [Service bindings](https://developers.cloudflare.com/workers/runtime-apis/bindings/service-bindings/) to make your application more modular, easier to maintain, and more performant. * Use [Custom Domains](https://developers.cloudflare.com/workers/configuration/routing/custom-domains/) when communicating with external APIs and services, which create a DNS record on your behalf and treat your Worker as an application instead of a proxy. ```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/configuration/","name":"Configuration"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/configuration/integrations/","name":"Integrations"}}]} ``` --- --- title: APIs description: To integrate with third party APIs from Cloudflare Workers, use the fetch API to make HTTP requests to the API endpoint. Then use the response data to modify or manipulate your content as needed. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/configuration/integrations/apis.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # APIs To integrate with third party APIs from Cloudflare Workers, use the [fetch API](https://developers.cloudflare.com/workers/runtime-apis/fetch/) to make HTTP requests to the API endpoint. Then use the response data to modify or manipulate your content as needed. For example, if you want to integrate with a weather API, make a fetch request to the API endpoint and retrieve the current weather data. Then use this data to display the current weather conditions on your website. To make the `fetch()` request, add the following code to your project's `src/index.js` file: JavaScript ``` async function handleRequest(request) { // Make the fetch request to the third party API endpoint const response = await fetch("https://weather-api.com/endpoint", { method: "GET", headers: { "Content-Type": "application/json", }, }); // Retrieve the data from the response const data = await response.json(); // Use the data to modify or manipulate your content as needed return new Response(data); } ``` Explain Code ## Authentication If your API requires authentication, use Wrangler secrets to securely store your credentials. To do this, create a secret in your Cloudflare Workers project using the following [wrangler secret](https://developers.cloudflare.com/workers/wrangler/commands/general/#secret) command: Terminal window ``` wrangler secret put SECRET_NAME ``` Then, retrieve the secret value in your code using the following code snippet: JavaScript ``` const secretValue = env.SECRET_NAME; ``` Then use the secret value to authenticate with the external service. For example, if the external service requires an API key for authentication, include it in your request headers. For services that require mTLS authentication, use [mTLS certificates](https://developers.cloudflare.com/workers/runtime-apis/bindings/mtls) to present a client certificate. ## Tips * Use the [Cache API](https://developers.cloudflare.com/workers/runtime-apis/cache/) to cache data from the third party API. This allows you to optimize cacheable requests made to the API. Integrating with third party APIs from Cloudflare Workers adds additional functionality and features to your application. * Use [Custom Domains](https://developers.cloudflare.com/workers/configuration/routing/custom-domains/) when communicating with external APIs, which treat your Worker as your core application. ```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/configuration/","name":"Configuration"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/configuration/integrations/","name":"Integrations"}},{"@type":"ListItem","position":5,"item":{"@id":"/workers/configuration/integrations/apis/","name":"APIs"}}]} ``` --- --- title: External Services description: Many external services provide libraries and SDKs to interact with their APIs. While many Node-compatible libraries work on Workers right out of the box, some, which implement fs, http/net, or access the browser window do not directly translate to the Workers runtime, which is v8-based. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/configuration/integrations/external-services.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # External Services Many external services provide libraries and SDKs to interact with their APIs. While many Node-compatible libraries work on Workers right out of the box, some, which implement `fs`, `http/net`, or access the browser `window` do not directly translate to the Workers runtime, which is v8-based. ## Authentication If your service requires authentication, use Wrangler secrets to securely store your credentials. To do this, create a secret in your Cloudflare Workers project using the following [wrangler secret](https://developers.cloudflare.com/workers/wrangler/commands/general/#secret) command: Terminal window ``` wrangler secret put SECRET_NAME ``` Then, retrieve the secret value in your code using the following code snippet: JavaScript ``` const secretValue = env.SECRET_NAME; ``` Then use the secret value to authenticate with the external service. For example, if the external service requires an API key for authentication, include the secret in your library's configuration. For services that require mTLS authentication, use [mTLS certificates](https://developers.cloudflare.com/workers/runtime-apis/bindings/mtls) to present a client certificate. Use [Custom Domains](https://developers.cloudflare.com/workers/configuration/routing/custom-domains/) when communicating with external APIs, which treat your Worker as your core application. ```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/configuration/","name":"Configuration"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/configuration/integrations/","name":"Integrations"}},{"@type":"ListItem","position":5,"item":{"@id":"/workers/configuration/integrations/external-services/","name":"External Services"}}]} ``` --- --- title: Multipart upload metadata description: If you're using the Workers Script Upload API or Version Upload API directly, multipart/form-data uploads require you to specify a metadata part. This metadata defines the Worker's configuration in JSON format, analogue to the wrangler.toml file. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/configuration/multipart-upload-metadata.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Multipart upload metadata Note There is a new API for uploading Workers. Refer to [these docs](https://developers.cloudflare.com/workers/platform/infrastructure-as-code#cloudflare-rest-api) for more information. If you're using the [Workers Script Upload API](https://developers.cloudflare.com/api/resources/workers/subresources/scripts/methods/update/) or [Version Upload API](https://developers.cloudflare.com/api/resources/workers/subresources/scripts/subresources/versions/methods/create/) directly, `multipart/form-data` uploads require you to specify a `metadata` part. This metadata defines the Worker's configuration in JSON format, analogue to the [wrangler.toml file](https://developers.cloudflare.com/workers/wrangler/configuration/). ## Sample `metadata` ``` { "main_module": "main.js", "bindings": [ { "type": "plain_text", "name": "MESSAGE", "text": "Hello, world!" } ], "compatibility_date": "2021-09-14" } ``` Explain Code Note See examples of metadata being used with the Workers Script Upload API [here](https://developers.cloudflare.com/workers/platform/infrastructure-as-code#cloudflare-rest-api). ## Attributes The following attributes are configurable at the top-level. Note At a minimum, the `main_module` key is required to upload a Worker. * `main_module` ` string ` required * The part name that contains the module entry point of the Worker that will be executed. For example, `main.js`. * `assets` ` object ` optional * [Asset](https://developers.cloudflare.com/workers/static-assets/) configuration for a Worker. * `config` ` object ` optional * [html\_handling](https://developers.cloudflare.com/workers/static-assets/routing/advanced/html-handling/) determines the redirects and rewrites of requests for HTML content. * [not\_found\_handling](https://developers.cloudflare.com/workers/static-assets/#routing-behavior) determines the response when a request does not match a static asset. * `jwt` field provides a token authorizing assets to be attached to a Worker. * `keep_assets` ` boolean ` optional * Specifies whether assets should be retained from a previously uploaded Worker version; used in lieu of providing a completion token. * `bindings` array\[object\] optional * [Bindings](#bindings) to expose in the Worker. * `placement` ` object ` optional * [Smart placement](https://developers.cloudflare.com/workers/configuration/placement/) object for the Worker. * `mode` field only supports `smart` for automatic placement. * `compatibility_date` ` string ` optional * [Compatibility Date](https://developers.cloudflare.com/workers/configuration/compatibility-dates/#setting-compatibility-date) indicating targeted support in the Workers runtime. Backwards incompatible fixes to the runtime following this date will not affect this Worker. Highly recommended to set a `compatibility_date`, otherwise if on upload via the API, it defaults to the oldest compatibility date before any flags took effect (2021-11-02). * `compatibility_flags` array\[string\] optional * [Compatibility Flags](https://developers.cloudflare.com/workers/configuration/compatibility-flags/#setting-compatibility-flags) that enable or disable certain features in the Workers runtime. Used to enable upcoming features or opt in or out of specific changes not included in a `compatibility_date`. ## Additional attributes: [Workers Script Upload API](https://developers.cloudflare.com/api/resources/workers/subresources/scripts/methods/update/) For [immediately deployed uploads](https://developers.cloudflare.com/workers/configuration/versions-and-deployments/#upload-a-new-version-and-deploy-it-immediately), the following **additional** attributes are configurable at the top-level. Note Except for `annotations`, these attributes are **not available** for version uploads. * `migrations` array\[object\] optional * [Durable Objects migrations](https://developers.cloudflare.com/durable-objects/reference/durable-objects-migrations/) to apply. * `logpush` ` boolean ` optional * Whether [Logpush](https://developers.cloudflare.com/cloudflare-for-platforms/cloudflare-for-saas/hostname-analytics/#logpush) is turned on for the Worker. * `tail_consumers` array\[object\] optional * [Tail Workers](https://developers.cloudflare.com/workers/observability/logs/tail-workers/) that will consume logs from the attached Worker. * `tags` array\[string\] optional * List of strings to use as tags for this Worker. * `annotations` ` object ` optional * Annotations object for the Worker version created by this upload. Also available on the [Version Upload API](#additional-attributes-version-upload-api). * `workers/message` specifies a custom message for the version. * `workers/tag` specifies a custom identifier for the version. ## Additional attributes: [Version Upload API](https://developers.cloudflare.com/api/resources/workers/subresources/scripts/subresources/versions/methods/create/) For [version uploads](https://developers.cloudflare.com/workers/configuration/versions-and-deployments/#upload-a-new-version-to-be-gradually-deployed-or-deployed-at-a-later-time), the following **additional** attributes are configurable at the top-level. * `annotations` ` object ` optional * Annotations object specific to the Worker version. * `workers/message` specifies a custom message for the version. * `workers/tag` specifies a custom identifier for the version. * `workers/alias` specifies a custom alias for this version. ## Bindings Workers can interact with resources on the Cloudflare Developer Platform using [bindings](https://developers.cloudflare.com/workers/runtime-apis/bindings/). Refer to the JSON example below that shows how to add bindings in the `metadata` part. ``` { "bindings": [ { "type": "ai", "name": "" }, { "type": "analytics_engine", "name": "", "dataset": "" }, { "type": "assets", "name": "" }, { "type": "browser_rendering", "name": "" }, { "type": "d1", "name": "", "id": "" }, { "type": "durable_object_namespace", "name": "", "class_name": "" }, { "type": "hyperdrive", "name": "", "id": "" }, { "type": "kv_namespace", "name": "", "namespace_id": "" }, { "type": "mtls_certificate", "name": "", "certificate_id": "" }, { "type": "plain_text", "name": "", "text": "" }, { "type": "queue", "name": "", "queue_name": "" }, { "type": "r2_bucket", "name": "", "bucket_name": "" }, { "type": "secret_text", "name": "", "text": "" }, { "type": "service", "name": "", "service": "", "environment": "production" }, { "type": "vectorize", "name": "", "index_name": "" }, { "type": "version_metadata", "name": "" } ] } ``` Explain Code ```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/configuration/","name":"Configuration"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/configuration/multipart-upload-metadata/","name":"Multipart upload metadata"}}]} ``` --- --- title: Placement description: Control where your Worker runs to reduce latency. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/configuration/placement.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Placement By default, [Workers](https://developers.cloudflare.com/workers/) and [Pages Functions](https://developers.cloudflare.com/pages/functions/) run in a data center closest to where the request was received. If your Worker makes requests to back-end infrastructure such as databases or APIs, it may be more performant to run that Worker closer to your back-end than the end user. * [ wrangler.jsonc ](#tab-panel-7245) * [ wrangler.toml ](#tab-panel-7246) JSONC ``` { "placement": { // Use one of the following options (mutually exclusive): "mode": "smart", // Cloudflare automatically places your Worker closest to the upstream with the most requests "region": "gcp:us-east4", // Explicit cloud region to run your Worker closest to - e.g. "gcp:us-east4" or "aws:us-east-1" "host": "db.example.com:5432", // A host to probe (TCP/layer 4) - e.g. a database host - and place your Worker closest to "hostname": "api.example.com", // A hostname to probe (HTTP/layer 7) - e.g. an API endpoint - and place your Worker closest to }, } ``` TOML ``` [placement] mode = "smart" region = "gcp:us-east4" host = "db.example.com:5432" hostname = "api.example.com" ``` Placement can reduce the overall latency of a Worker request by minimizing roundtrip latency of requests between your Worker and back-end services. You can achieve single-digit millisecond latency to databases, APIs, and other services running in legacy cloud infrastructure. | Option | Best for | Configuration | | ---------- | --------------------------------------------------------------- | ---------------- | | **Smart** | Multiple back-end services, or unknown infrastructure locations | mode = "smart" | | **Region** | Single back-end service in a known cloud region | region | | **Host** | Single back-end service not in a major cloud provider | host or hostname | ## Understand placement Consider a user in Sydney, Australia accessing an application running on Workers. This application makes multiple round trips to a database in Frankfurt, Germany. ![A user located in Sydney, AU connecting to a Worker in the same region which then makes multiple round trips to a database located in Frankfurt, DE. ](https://developers.cloudflare.com/_astro/workers-smart-placement-disabled.CgvAE24H_2lFyUf.webp) The latency from multiple round trips between Sydney and Frankfurt adds up. By placing the Worker near the database, Cloudflare reduces the total request duration. ![A user located in Sydney, AU connecting to a Worker in Frankfurt, DE which then makes multiple round trips to a database also located in Frankfurt, DE. ](https://developers.cloudflare.com/_astro/workers-smart-placement-enabled.D6RN33at_Z2gprT.webp) ## Enable Smart Placement Smart Placement automatically analyzes your Worker's traffic patterns and places it in an optimal location. Use Smart Placement when: * Your Worker connects to multiple back-end services * You do not know the exact location of your infrastructure * Your back-end services are distributed or replicated Smart Placement is enabled on a per-Worker basis. Once enabled, it analyzes the [request duration](https://developers.cloudflare.com/workers/observability/metrics-and-analytics/#request-duration) of the Worker in different Cloudflare locations on a regular basis. For each candidate location, Smart Placement considers the Worker's performance and the network latency added by forwarding the request. If a candidate location is significantly faster, the request is forwarded there. Otherwise, the Worker runs in the default location closest to the request. Smart Placement only considers locations where the Worker has previously run. It cannot place your Worker in a location that does not normally receive traffic. ### Review limitations * Smart Placement only affects the execution of [fetch event handlers](https://developers.cloudflare.com/workers/runtime-apis/handlers/fetch/). It does not affect [RPC methods](https://developers.cloudflare.com/workers/runtime-apis/rpc/) or [named entrypoints](https://developers.cloudflare.com/workers/runtime-apis/bindings/service-bindings/rpc/#named-entrypoints). * Workers without a fetch event handler are ignored by Smart Placement. * [Static assets](https://developers.cloudflare.com/workers/static-assets/) are always served from the location nearest to the incoming request. If your code retrieves assets via the [static assets binding](https://developers.cloudflare.com/workers/static-assets/binding/), assets are served from the location where your Worker runs. ### Enable smart placement Smart Placement is available on all Workers plans. #### Configure with Wrangler Add the following to your Wrangler configuration file: * [ wrangler.jsonc ](#tab-panel-7243) * [ wrangler.toml ](#tab-panel-7244) JSONC ``` { "placement": { "mode": "smart", }, } ``` TOML ``` [placement] mode = "smart" ``` Smart Placement may take up to 15 minutes to analyze your Worker after deployment. #### Configure in the dashboard 1. Go to **Workers & Pages**. [ Go to **Workers & Pages** ](https://dash.cloudflare.com/?to=/:account/workers-and-pages) 2. Select your Worker. 3. Go to **Settings** \> **General**. 4. Under **Placement**, select **Smart**. Smart Placement requires consistent traffic to the Worker from multiple locations to make a placement decision. The analysis process may take up to 15 minutes. ### Check placement status Query your Worker's placement status through the Workers API: Terminal window ``` curl -X GET https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/workers/services/$WORKER_NAME \ -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \ -H "Content-Type: application/json" | jq . ``` Possible placement states: | Status | Description | | ------------------------- | ----------------------------------------------------------------------------------------------------------------- | | _(not present)_ | The Worker has not been analyzed yet. It runs in the default location closest to the request. | | SUCCESS | The Worker was analyzed and will be optimized by Smart Placement. | | INSUFFICIENT\_INVOCATIONS | The Worker has not received enough requests from multiple locations to make a placement decision. | | UNSUPPORTED\_APPLICATION | Smart Placement made the Worker slower and reverted the placement. This state is rare (fewer than 1% of Workers). | ### Review request duration analytics Once Smart Placement is enabled, data about request duration is collected. Request duration is measured at the data center closest to the end user. By default, 1% of requests are not routed with Smart Placement to serve as a baseline for comparison. View your Worker's [request duration analytics](https://developers.cloudflare.com/workers/observability/metrics-and-analytics/#request-duration) to measure the impact of Smart Placement. ### Check the `cf-placement` header Cloudflare adds a `cf-placement` header to all requests when placement is enabled. Use this header to check whether a request was routed with Smart Placement and where the Worker processed the request. The header value includes a placement type and an airport code indicating the data center location: * `remote-LHR` — The request was routed using Smart Placement to a data center near London. * `local-EWR` — The request was not routed using Smart Placement. The Worker ran in the default location near Newark. Warning The `cf-placement` header may be removed before Smart Placement exits beta. ## Configure explicit Placement Hints Placement Hints let you explicitly specify where your Worker runs. Use Placement Hints when: * You know the exact location of your back-end infrastructure * Your Worker connects to a single database, API, or service * Your infrastructure is single-homed (not replicated or anycasted) Examples include a primary database, a virtual machine, or a Kubernetes cluster in a specific region. Reducing round-trip latency from 20 to 30 milliseconds per query to 1 to 3 milliseconds improves response times. Note Workers run on [Cloudflare's global network ↗](https://www.cloudflare.com/network/), not inside cloud provider regions. Placement Hints run your Worker in the data center with the lowest latency to your specified cloud region. At extremely high request volumes (hundreds of thousands of requests per second or more), Cloudflare may run instances across a more distributed area to balance traffic. ### Specify a cloud region If your infrastructure runs in AWS, GCP, or Azure, set the `placement.region` property using the format `{provider}:{region}`: * [ wrangler.jsonc ](#tab-panel-7247) * [ wrangler.toml ](#tab-panel-7248) JSONC ``` { "placement": { "region": "aws:us-east-1", // Explicit cloud region to run your Worker closest to - e.g. "gcp:us-east4" or "aws:us-east-1" }, } ``` TOML ``` [placement] region = "aws:us-east-1" ``` Cloudflare maps your specified cloud region to the data center with the lowest latency to that region. Cloudflare automatically adjusts placement to account for network maintenance or changes, so you do not need to specify failover regions. ### Specify a host endpoint If your infrastructure is not in a major cloud provider, you can specify an endpoint for Cloudflare to probe. Cloudflare will triangulate the position of your external host and place Workers in a nearby region. Note Host-based placement is experimental. Set `placement.host` to identify a layer 4 service. Cloudflare uses TCP CONNECT checks to measure latency and selects the best data center. * [ wrangler.jsonc ](#tab-panel-7249) * [ wrangler.toml ](#tab-panel-7250) JSONC ``` { "placement": { "host": "my_database_host.com:5432", // A host to probe (TCP/layer 4) - e.g. a database host - and place your Worker closest to }, } ``` TOML ``` [placement] host = "my_database_host.com:5432" ``` Set `placement.hostname` to identify a layer 7 service. Cloudflare uses HTTP HEAD checks to measure latency and selects the best data center. * [ wrangler.jsonc ](#tab-panel-7251) * [ wrangler.toml ](#tab-panel-7252) JSONC ``` { "placement": { "hostname": "my_api_server.com", // A hostname to probe (HTTP/layer 7) - e.g. an API endpoint - and place your Worker closest to }, } ``` TOML ``` [placement] hostname = "my_api_server.com" ``` Probes are sent from public IP ranges, not Cloudflare IP ranges. Cloudflare rechecks service location at regular intervals. These probes locate single-homed resources and do not work correctly for broadcast, anycast, multicast, or replicated resources. ### List supported regions Placement Hints support Amazon Web Services (AWS), Google Cloud Platform (GCP), and Microsoft Azure region identifiers: | Provider | Format | Examples | | -------- | -------------- | --------------------------------------------------- | | AWS | aws:{region} | aws:us-east-1, aws:us-west-2, aws:eu-central-1 | | GCP | gcp:{region} | gcp:us-east4, gcp:europe-west1, gcp:asia-east1 | | Azure | azure:{region} | azure:westeurope, azure:eastus, azure:southeastasia | For a full list of region codes, refer to [AWS regions ↗](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-regions-availability-zones.html), [GCP regions ↗](https://cloud.google.com/compute/docs/regions-zones), or [Azure regions ↗](https://learn.microsoft.com/en-us/azure/reliability/regions-list). ## Placement Behavior Workers placement behaves in similar fashion when either Smart Placement or Placement Hints are used. The following behavior applies to both. ### Review limitations The following limitations apply to both Smart Placement and Placement Hints: * Placement only affects the execution of [fetch event handlers](https://developers.cloudflare.com/workers/runtime-apis/handlers/fetch/). It does not affect [RPC methods](https://developers.cloudflare.com/workers/runtime-apis/rpc/) or [named entrypoints](https://developers.cloudflare.com/workers/runtime-apis/bindings/service-bindings/rpc/#named-entrypoints). * Workers without a fetch event handler are ignored by placement. * [Static assets](https://developers.cloudflare.com/workers/static-assets/) are always served from the location nearest to the incoming request. If your code retrieves assets via the [static assets binding](https://developers.cloudflare.com/workers/static-assets/binding/), assets are served from the location where your Worker runs. ### `cf-placement` header Cloudflare adds a `cf-placement` header to all requests when placement is enabled. Use this header to check whether a request was routed with placement and where the Worker processed the request. The header value includes a placement type and an airport code indicating the data center location: * `remote-LHR` — The request was routed using Smart Placement to a data center near London. * `local-EWR` — The request was not routed using Smart Placement. The Worker ran in the default location near Newark. Warning The `cf-placement` header may be removed before Smart Placement exits beta. ## Multiple Workers If you are building full-stack applications on Workers, split your edge logic (authentication, routing) and back-end logic (database queries, API calls) into separate Workers. Use [Service Bindings](https://developers.cloudflare.com/workers/runtime-apis/bindings/service-bindings/) to connect them with type-safe RPC. ![Smart Placement and Service Bindings](https://developers.cloudflare.com/_astro/smart-placement-service-bindings.Ce58BYeF_ZmD4l8.webp) Enable placement on your back-end Worker to invoke it close to your database, while the edge Worker handles authentication close to the user. ### Example: Edge authentication with a placed back-end This example shows two Workers: * `auth-worker` — runs at the edge (no placement), handles authentication * `app-worker` — placed near your database, handles data queries * [ auth-worker ](#tab-panel-7257) * [ app-worker ](#tab-panel-7258) * [ wrangler.jsonc ](#tab-panel-7253) * [ wrangler.toml ](#tab-panel-7254) JSONC ``` { "name": "auth-worker", "main": "src/index.ts", "services": [{ "binding": "APP", "service": "app-worker" }], } ``` TOML ``` name = "auth-worker" main = "src/index.ts" [[services]] binding = "APP" service = "app-worker" ``` auth-worker/src/index.ts ``` import { AppWorker } from "../app-worker/src/index"; interface Env { APP: Service; } export default { async fetch(request: Request, env: Env): Promise { const authHeader = request.headers.get("Authorization"); if (!authHeader?.startsWith("Bearer ")) { return new Response("Unauthorized", { status: 401 }); } const userId = await validateToken(authHeader.slice(7)); if (!userId) { return new Response("Invalid token", { status: 403 }); } // Call the placed back-end Worker via RPC const data = await env.APP.getUser(userId); return Response.json(data); }, }; async function validateToken(token: string): Promise { return token === "valid" ? "user-123" : null; } ``` Explain Code * [ wrangler.jsonc ](#tab-panel-7255) * [ wrangler.toml ](#tab-panel-7256) JSONC ``` { "name": "app-worker", "main": "src/index.ts", "placement": { // Use one of the following options (mutually exclusive): // "mode": "smart", // Cloudflare automatically places your Worker closest to the upstream with the most requests "region": "aws:us-east-1", // Explicit cloud region to run your Worker closest to - e.g. "gcp:us-east4" or "aws:us-east-1" // "host": "db.example.com:5432", // A host to probe (TCP/layer 4) - e.g. a database host - and place your Worker closest to // "hostname": "api.example.com", // A hostname to probe (HTTP/layer 7) - e.g. an API endpoint - and place your Worker closest to }, } ``` Explain Code TOML ``` name = "app-worker" main = "src/index.ts" [placement] region = "aws:us-east-1" ``` app-worker/src/index.ts ``` import { WorkerEntrypoint } from "cloudflare:workers"; export default class AppWorker extends WorkerEntrypoint { async fetch() { return new Response(null, { status: 404 }); } // Each method runs near your database - multiple queries stay fast async getUser(userId: string) { const user = await this.env.DB.prepare("SELECT * FROM users WHERE id = ?") .bind(userId) .first(); return user; } async getUserListings(userId: string) { // Multiple round-trips to the DB are low-latency when placed nearby const user = await this.env.DB.prepare("SELECT * FROM users WHERE id = ?") .bind(userId) .first(); const listings = await this.env.DB.prepare( "SELECT * FROM listings WHERE owner_id = ?", ) .bind(userId) .all(); const reviews = await this.env.DB.prepare( "SELECT * FROM reviews WHERE listing_id IN (SELECT id FROM listings WHERE owner_id = ?)", ) .bind(userId) .all(); return { user, listings: listings.results, reviews: reviews.results }; } } ``` Explain Code The `auth-worker` runs at the edge to reject unauthorized requests quickly. Authenticated requests are forwarded via RPC to `app-worker`, which runs near your database for fast queries. ### Durable Objects [Durable Objects](https://developers.cloudflare.com/durable-objects/) provide automatic placement without configuration. Queries to a Durable Object's embedded [SQLite database](https://developers.cloudflare.com/durable-objects/api/sqlite-storage-api/) are effectively [zero-latency ↗](https://blog.cloudflare.com/sqlite-in-durable-objects/) because compute runs in the same process as the data. Do as much work as possible within the Durable Object and return a composite result, rather than making multiple round-trips from your Worker: src/index.ts ``` import { DurableObject } from "cloudflare:workers"; type Session = { id: string; user_id: string; created_at: number }; type PromptHistory = { id: string; session_id: string; role: string; content: string; }; export class AgentHistory extends DurableObject { async getSessionContext(sessionId: string) { // All queries execute with zero network latency — compute and data are colocated const session = this.ctx.storage.sql .exec("SELECT * FROM sessions WHERE id = ?", sessionId) .one(); const prompts = this.ctx.storage.sql .exec( "SELECT * FROM prompt_history WHERE session_id = ? ORDER BY created_at", sessionId, ) .toArray(); return { session, prompts }; } } ``` Explain Code ```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/configuration/","name":"Configuration"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/configuration/placement/","name":"Placement"}}]} ``` --- --- title: Preview URLs description: Preview URLs allow you to preview new versions of your project without deploying it to production. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/configuration/previews.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Preview URLs # Overview Preview URLs allow you to preview new versions of your Worker without deploying it to production. There are two types of preview URLs: * **Versioned Preview URLs**: A unique URL generated automatically for each new version of your Worker. * **Aliased Preview URLs**: A static, human-readable alias that you can manually assign to a Worker version. Both preview URL types follow the format: `-..workers.dev`. Preview URLs can be: * Integrated into CI/CD pipelines, allowing automatic generation of preview environments for every pull request. * Used for collaboration between teams to test code changes in a live environment and verify updates. * Used to test new API endpoints, validate data formats, and ensure backward compatibility with existing services. When testing zone level performance or security features for a version, we recommend using [version overrides](https://developers.cloudflare.com/workers/configuration/versions-and-deployments/gradual-deployments/#version-overrides) so that your zone's performance and security settings apply. Note Preview URLs are only available for Worker versions uploaded after 2024-09-25. ## Types of Preview URLs ### Versioned Preview URLs Every time you create a new [version](https://developers.cloudflare.com/workers/configuration/versions-and-deployments/#versions) of your Worker, a unique static version preview URL is generated automatically. These URLs use a version prefix and follow the format `-..workers.dev`. New versions of a Worker are created when you run: * [wrangler deploy](https://developers.cloudflare.com/workers/wrangler/commands/general/#deploy) * [wrangler versions upload](https://developers.cloudflare.com/workers/wrangler/commands/general/#versions-upload) * Or when you make edits via the Cloudflare dashboard If Preview URLs have been enabled, they are public and available immediately after version creation. Note Minimum required Wrangler version: 3.74.0\. Check your version by running `wrangler --version`. To update Wrangler, refer to [Install/Update Wrangler](https://developers.cloudflare.com/workers/wrangler/install-and-update/). #### View versioned preview URLs using Wrangler The [wrangler versions upload](https://developers.cloudflare.com/workers/wrangler/commands/general/#versions-upload) command uploads a new [version](https://developers.cloudflare.com/workers/configuration/versions-and-deployments/#versions) of your Worker and returns a preview URL for each version uploaded. #### View versioned preview URLs on the Workers dashboard 1. In the Cloudflare dashboard, go to the **Workers & Pages** page. [ Go to **Workers & Pages** ](https://dash.cloudflare.com/?to=/:account/workers-and-pages) 2. Select your Worker. 3. Go to the **Deployments** tab, and find the version you would like to view. ### Aliased preview URLs Aliased preview URLs let you assign a persistent, readable alias to a specific Worker version. These are useful for linking to stable previews across many versions (e.g. to share an upcoming but still actively being developed new feature). A common workflow would be to assign an alias for the branch that you're working on. These types of preview URLs follow the same pattern as other preview URLs:`-..workers.dev` Note Minimum required Wrangler version: `4.21.0`. Check your version by running `wrangler --version`. To update Wrangler, refer to [Install/Update Wrangler](https://developers.cloudflare.com/workers/wrangler/install-and-update/). #### Create an Alias Aliases may be created during `versions upload`, by providing the `--preview-alias` flag with a valid alias name: Terminal window ``` wrangler versions upload --preview-alias staging ``` The resulting alias would be associated with this version, and immediately available at:`staging-..workers.dev` #### Rules and limitations * Aliases may only be created during version upload. * Aliases must use only lowercase letters, numbers, and dashes. * Aliases must begin with a lowercase letter. * The alias and Worker name combined (with a dash) must not exceed 63 characters due to DNS label limits. * Only the 1000 most recently deployed aliases are retained. When a new alias is created beyond this limit, the least recently deployed alias is deleted. ## Manage access to Preview URLs When enabled, all preview URLs are available publicly. You can use [Cloudflare Access](https://developers.cloudflare.com/cloudflare-one/access-controls/policies/) to require visitors to authenticate before accessing preview URLs. You can limit access to yourself, your teammates, your organization, or anyone else you specify in your [access policy](https://developers.cloudflare.com/cloudflare-one/access-controls/policies/). To limit your preview URLs to authorized emails only: 1. In the Cloudflare dashboard, go to the **Workers & Pages** page. [ Go to **Workers & Pages** ](https://dash.cloudflare.com/?to=/:account/workers-and-pages) 2. In **Overview**, select your Worker. 3. Go to **Settings** \> **Domains & Routes**. 4. For Preview URLs, click **Enable Cloudflare Access**. 5. Optionally, to configure the Access application, click **Manage Cloudflare Access**. There, you can change the email addresses you want to authorize. View [Access policies](https://developers.cloudflare.com/cloudflare-one/access-controls/policies/#selectors) to learn about configuring alternate rules. 6. [Validate the Access JWT ↗](https://developers.cloudflare.com/cloudflare-one/access-controls/applications/http-apps/authorization-cookie/validating-json/#cloudflare-workers-example) in your Worker script using the audience (`aud`) tag and JWKs URL provided. ## Toggle Preview URLs (Enable or Disable) Note: * Preview URLs are enabled by default when `workers_dev` is enabled. * Preview URLs are disabled by default when `workers_dev` is disabled. * Disabling Preview URLs will disable routing to both versioned and aliased preview URLs. ### From the Dashboard To toggle Preview URLs for a Worker: 1. In the Cloudflare dashboard, go to the **Workers & Pages** page. [ Go to **Workers & Pages** ](https://dash.cloudflare.com/?to=/:account/workers-and-pages) 2. In **Overview**, select your Worker. 3. Go to **Settings** \> **Domains & Routes**. 4. For Preview URLs, click **Enable** or **Disable**. 5. Confirm your action. ### From the [Wrangler configuration file](https://developers.cloudflare.com/workers/wrangler/configuration/) Note Wrangler 3.91.0 or higher is required to use this feature. Note Older Wrangler versions will default to Preview URLs being enabled. To toggle Preview URLs for a Worker, include any of the following in your Worker's Wrangler file: * [ wrangler.jsonc ](#tab-panel-7259) * [ wrangler.toml ](#tab-panel-7260) JSONC ``` { "preview_urls": true } ``` TOML ``` preview_urls = true ``` * [ wrangler.jsonc ](#tab-panel-7261) * [ wrangler.toml ](#tab-panel-7262) JSONC ``` { "preview_urls": false } ``` TOML ``` preview_urls = false ``` If not given, `preview_urls = workers_dev` is the default. Warning If you enable or disable Preview URLs in the Cloudflare dashboard, but do not update your Worker's Wrangler file accordingly, the Preview URLs status will change the next time you deploy your Worker with Wrangler. ## Limitations * Preview URLs are not generated for Workers that implement a [Durable Object](https://developers.cloudflare.com/durable-objects/). * Preview URLs are not currently generated for [Workers for Platforms](https://developers.cloudflare.com/cloudflare-for-platforms/workers-for-platforms/) [user Workers](https://developers.cloudflare.com/cloudflare-for-platforms/workers-for-platforms/how-workers-for-platforms-works/#user-workers). This is a temporary limitation, we are working to remove it. * You cannot currently configure Preview URLs to run on a subdomain other than [workers.dev](https://developers.cloudflare.com/workers/configuration/routing/workers-dev/). * You cannot view logs for Preview URLs today, this includes Workers Logs, Wrangler tail and Logpush. ```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/configuration/","name":"Configuration"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/configuration/previews/","name":"Preview URLs"}}]} ``` --- --- title: Routes and domains description: Connect your Worker to an external endpoint (via Routes, Custom Domains or a `workers.dev` subdomain) such that it can be accessed by the Internet. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/configuration/routing/index.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Routes and domains To allow a Worker to receive inbound HTTP requests, you must connect it to an external endpoint such that it can be accessed by the Internet. There are three types of routes: * [Custom Domains](https://developers.cloudflare.com/workers/configuration/routing/custom-domains): Routes to a domain or subdomain (such as `example.com` or `shop.example.com`) within a Cloudflare zone where the Worker is the origin. * [Routes](https://developers.cloudflare.com/workers/configuration/routing/routes/): Routes that are set within a Cloudflare zone where your origin server, if you have one, is behind a Worker that the Worker can communicate with. * [workers.dev](https://developers.cloudflare.com/workers/configuration/routing/workers-dev/): A `workers.dev` subdomain route is automatically created for each Worker to help you getting started quickly. You may choose to [disable](https://developers.cloudflare.com/workers/configuration/routing/workers-dev/) your `workers.dev` subdomain. ## What is best for me? It's recommended to run production Workers on a [Workers route or custom domain](https://developers.cloudflare.com/workers/configuration/routing/), rather than on your `workers.dev` subdomain. Your `workers.dev` subdomain is treated as a [Free website ↗](https://www.cloudflare.com/plans/) and is intended for personal or hobby projects that aren't business-critical. Custom Domains are recommended for use cases where your Worker is your application's origin server. Custom Domains can also be invoked within the same zone via `fetch()`, unlike Routes. Routes are recommended for use cases where your application's origin server is external to Cloudflare. Note that Routes cannot be the target of a same-zone `fetch()` call. ```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/configuration/","name":"Configuration"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/configuration/routing/","name":"Routes and domains"}}]} ``` --- --- title: Custom Domains description: Custom Domains allow you to connect your Worker to a domain or subdomain, without having to make changes to your DNS settings or perform any certificate management. After you set up a Custom Domain for your Worker, Cloudflare will create DNS records and issue necessary certificates on your behalf. The created DNS records will point directly to your Worker. Unlike Routes, Custom Domains point all paths of a domain or subdomain to your Worker. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/configuration/routing/custom-domains.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Custom Domains ## Background Custom Domains allow you to connect your Worker to a domain or subdomain, without having to make changes to your DNS settings or perform any certificate management. After you set up a Custom Domain for your Worker, Cloudflare will create DNS records and issue necessary certificates on your behalf. The created DNS records will point directly to your Worker. Unlike [Routes](https://developers.cloudflare.com/workers/configuration/routing/routes/#set-up-a-route), Custom Domains point all paths of a domain or subdomain to your Worker. Custom Domains are routes to a domain or subdomain (such as `example.com` or `shop.example.com`) within a Cloudflare zone where the Worker is the origin. Custom Domains are recommended if you want to connect your Worker to the Internet and do not have an application server that you want to always communicate with. If you do have external dependencies, you can create a `Request` object with the target URI, and use `fetch()` to reach out. Custom Domains can stack on top of each other. For example, if you have Worker A attached to `app.example.com` and Worker B attached to `api.example.com`, Worker A can call `fetch()` on `api.example.com` and invoke Worker B. ![Custom Domains can stack on top of each other, like any external dependencies](https://developers.cloudflare.com/_astro/custom-domains-subrequest.C6c84jN5_1oQWRD.webp) Custom Domains can also be invoked within the same zone via `fetch()`, unlike Routes. ## Add a Custom Domain To add a Custom Domain, you must have: 1. An [active Cloudflare zone](https://developers.cloudflare.com/dns/zone-setups/). 2. A Worker to invoke. Custom Domains can be attached to your Worker via the Cloudflare dashboard, [Wrangler](https://developers.cloudflare.com/workers/configuration/routing/custom-domains/#set-up-a-custom-domain-in-your-wrangler-configuration-file) or the [API](https://developers.cloudflare.com/api/resources/workers/subresources/domains/methods/list/). Warning You cannot create a Custom Domain on a hostname with an existing CNAME DNS record or on a zone you do not own. ### Set up a Custom Domain in the dashboard To set up a Custom Domain in the dashboard: 1. In the Cloudflare dashboard, go to the **Workers & Pages** page. [ Go to **Workers & Pages** ](https://dash.cloudflare.com/?to=/:account/workers-and-pages) 2. In **Overview**, select your Worker. 3. Go to **Settings** \> **Domains & Routes** \> **Add** \> **Custom Domain**. 4. Enter the domain you want to configure for your Worker. 5. Select **Add Custom Domain**. After you have added the domain or subdomain, Cloudflare will create a new DNS record for you. You can add multiple Custom Domains. ### Set up a Custom Domain in your Wrangler configuration file To configure a Custom Domain in your [Wrangler configuration file](https://developers.cloudflare.com/workers/wrangler/configuration/), add the `custom_domain=true` option on each pattern under `routes`. For example, to configure a Custom Domain: * [ wrangler.jsonc ](#tab-panel-7263) * [ wrangler.toml ](#tab-panel-7264) JSONC ``` { "routes": [ { "pattern": "shop.example.com", "custom_domain": true } ] } ``` TOML ``` [[routes]] pattern = "shop.example.com" custom_domain = true ``` To configure multiple Custom Domains: * [ wrangler.jsonc ](#tab-panel-7267) * [ wrangler.toml ](#tab-panel-7268) JSONC ``` { "routes": [ { "pattern": "shop.example.com", "custom_domain": true }, { "pattern": "shop-two.example.com", "custom_domain": true } ] } ``` Explain Code TOML ``` [[routes]] pattern = "shop.example.com" custom_domain = true [[routes]] pattern = "shop-two.example.com" custom_domain = true ``` ## Worker to Worker communication On the same zone, the only way for a Worker to communicate with another Worker running on a [route](https://developers.cloudflare.com/workers/configuration/routing/routes/#set-up-a-route), or on a [workers.dev](https://developers.cloudflare.com/workers/configuration/routing/routes/#%5Ftop) subdomain, is via [service bindings](https://developers.cloudflare.com/workers/runtime-apis/bindings/service-bindings/). On the same zone, if a Worker is attempting to communicate with a target Worker running on a Custom Domain rather than a route, the limitation is removed. Fetch requests sent on the same zone from one Worker to another Worker running on a Custom Domain will succeed without a service binding. For example, consider the following scenario, where both Workers are running on the `example.com` Cloudflare zone: * `worker-a` running on the [route](https://developers.cloudflare.com/workers/configuration/routing/routes/#set-up-a-route) `auth.example.com/*`. * `worker-b` running on the [route](https://developers.cloudflare.com/workers/configuration/routing/routes/#set-up-a-route) `shop.example.com/*`. If `worker-a` sends a fetch request to `worker-b`, the request will fail, because of the limitation on same-zone fetch requests. `worker-a` must have a service binding to `worker-b` for this request to resolve. worker-a ``` export default { fetch(request) { // This will fail return fetch("https://shop.example.com") } } ``` However, if `worker-b` was instead set up to run on the Custom Domain `shop.example.com`, the fetch request would succeed. ## Request matching behaviour Custom Domains do not support [wildcard DNS records](https://developers.cloudflare.com/dns/manage-dns-records/reference/wildcard-dns-records/). An incoming request must exactly match the domain or subdomain your Custom Domain is registered to. Other parts (path, query parameters) of the URL are not considered when executing this matching logic. For example, if you create a Custom Domain on `api.example.com` attached to your `api-gateway` Worker, a request to either `api.example.com/login` or `api.example.com/user` would invoke the same `api-gateway` Worker. ![Custom Domains follow standard DNS ordering and matching logic](https://developers.cloudflare.com/_astro/custom-domains-api-gateway.DmeJZDoL_Z1d0vv1.webp) ## Interaction with Routes A Worker running on a Custom Domain is treated as an origin. Any Workers running on routes before your Custom Domain can optionally call the Worker registered on your Custom Domain by issuing `fetch(request)` with the incoming `Request` object. That means that you are able to set up Workers to run before a request gets to your Custom Domain Worker. In other words, you can chain together two Workers in the same request. For example, consider the following workflow: 1. A Custom Domain for `api.example.com` points to your `api-worker` Worker. 2. A route added to `api.example.com/auth` points to your `auth-worker` Worker. 3. A request to `api.example.com/auth` will trigger your `auth-worker` Worker. 4. Using `fetch(request)` within the `auth-worker` Worker will invoke the `api-worker` Worker, as if it was a normal application server. auth-worker ``` export default { fetch(request) { const url = new URL(request.url) if(url.searchParams.get("auth") !== "SECRET_TOKEN") { return new Response(null, { status: 401 }) } else { // This will invoke `api-worker` return fetch(request) } } } ``` Explain Code ## Certificates Creating a Custom Domain will also generate an [Advanced Certificate](https://developers.cloudflare.com/ssl/edge-certificates/advanced-certificate-manager/) on your target zone for your target hostname. These certificates are generated with default settings. To override these settings, delete the generated certificate and create your own certificate in the Cloudflare dashboard. Refer to [Manage advanced certificates](https://developers.cloudflare.com/ssl/edge-certificates/advanced-certificate-manager/manage-certificates/) for instructions. ## Migrate from Routes If you are currently invoking a Worker using a [route](https://developers.cloudflare.com/workers/configuration/routing/routes/) with `/*`, and you have a CNAME record pointing to `100::` or similar, a Custom Domain is a recommended replacement. ### Migrate from Routes via the dashboard To migrate the route `example.com/*`: 1. In the Cloudflare dashboard, go to the **DNS Records** page for your domain. [ Go to **Records** ](https://dash.cloudflare.com/?to=/:account/:zone/dns/records) 2. Delete the CNAME record for `example.com`. 3. Go to **Account Home** \> **Workers & Pages**. 4. In **Overview**, select your Worker > **Settings** \> **Domains & Routes**. 5. Select **Add** \> **Custom domain** and add `example.com`. 6. Delete the route `example.com/*` located in your Worker > **Settings** \> **Domains & Routes**. ### Migrate from Routes via Wrangler To migrate the route `example.com/*` in your [Wrangler configuration file](https://developers.cloudflare.com/workers/wrangler/configuration/): 1. In the Cloudflare dashboard, go to the **DNS Records** page for your domain. [ Go to **Records** ](https://dash.cloudflare.com/?to=/:account/:zone/dns/records) 2. Delete the CNAME record for `example.com`. 3. Add the following to your Wrangler file: * [ wrangler.jsonc ](#tab-panel-7265) * [ wrangler.toml ](#tab-panel-7266) JSONC ``` { "routes": [ { "pattern": "example.com", "custom_domain": true } ] } ``` TOML ``` [[routes]] pattern = "example.com" custom_domain = true ``` 4. Run `npx wrangler deploy` to create the Custom Domain your Worker will run on. ```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/configuration/","name":"Configuration"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/configuration/routing/","name":"Routes and domains"}},{"@type":"ListItem","position":5,"item":{"@id":"/workers/configuration/routing/custom-domains/","name":"Custom Domains"}}]} ``` --- --- title: Routes description: Routes allow users to map a URL pattern to a Worker. When a request comes in to the Cloudflare network that matches the specified URL pattern, your Worker will execute on that route. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/configuration/routing/routes.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Routes ## Background Routes allow users to map a URL pattern to a Worker. When a request comes in to the Cloudflare network that matches the specified URL pattern, your Worker will execute on that route. Routes are a set of rules that evaluate against a request's URL. Routes are recommended for you if you have a designated application server you always need to communicate with. Calling `fetch()` on the incoming `Request` object will trigger a subrequest to your application server, as defined in the **DNS** settings of your Cloudflare zone. Routes add Workers functionality to your existing proxied hostnames, in front of your application server. These allow your Workers to act as a proxy and perform any necessary work before reaching out to an application server behind Cloudflare. ![Routes work with your applications defined in Cloudflare DNS](https://developers.cloudflare.com/_astro/routes-diagram.CfGSi1RG_Z1jppef.webp) Routes can `fetch()` Custom Domains and take precedence if configured on the same hostname. If you would like to run a logging Worker in front of your application, for example, you can create a Custom Domain on your application Worker for `app.example.com`, and create a Route for your logging Worker at `app.example.com/*`. Calling `fetch()` will invoke the application Worker on your Custom Domain. Note that Routes cannot be the target of a same-zone `fetch()` call. ## Set up a route To add a route, you must have: 1. An [active Cloudflare zone](https://developers.cloudflare.com/dns/zone-setups/). 2. A Worker to invoke. 3. A DNS record set up for the [domain](https://developers.cloudflare.com/dns/manage-dns-records/how-to/create-zone-apex/) or [subdomain](https://developers.cloudflare.com/dns/manage-dns-records/how-to/create-subdomain/) proxied by Cloudflare (also known as orange-clouded) you would like to route to. Warning Route setup will differ depending on if your application's origin is a Worker or not. If your Worker is your application's origin, use [Custom Domains](https://developers.cloudflare.com/workers/configuration/routing/custom-domains/). If your Worker is not your application's origin, follow the instructions below to set up a route. Note Routes can also be created via the API. Refer to the [Workers Routes API documentation](https://developers.cloudflare.com/api/resources/workers/subresources/routes/methods/create/) for more information. ### Set up a route in the dashboard Before you set up a route, make sure you have a DNS record set up for the [domain](https://developers.cloudflare.com/dns/manage-dns-records/how-to/create-zone-apex/) or [subdomain](https://developers.cloudflare.com/dns/manage-dns-records/how-to/create-subdomain/) you would like to route to. To set up a route in the dashboard: 1. In the Cloudflare dashboard, go to the **Workers & Pages** page. [ Go to **Workers & Pages** ](https://dash.cloudflare.com/?to=/:account/workers-and-pages) 2. In **Overview**, select your Worker. 3. Go to **Settings** \> **Domains & Routes** \> **Add** \> **Route**. 4. Select the zone and enter the route pattern. 5. Select **Add route**. ### Set up a route in the Wrangler configuration file Before you set up a route, make sure you have a DNS record set up for the [domain](https://developers.cloudflare.com/dns/manage-dns-records/how-to/create-zone-apex/) or [subdomain](https://developers.cloudflare.com/dns/manage-dns-records/how-to/create-subdomain/) you would like to route to. To configure a route using your [Wrangler configuration file](https://developers.cloudflare.com/workers/wrangler/configuration/), refer to the following example. * [ wrangler.jsonc ](#tab-panel-7269) * [ wrangler.toml ](#tab-panel-7270) JSONC ``` { "routes": [ { "pattern": "subdomain.example.com/*", "zone_name": "example.com" }, // or { "pattern": "subdomain.example.com/*", "zone_id": "" } ] } ``` Explain Code TOML ``` [[routes]] pattern = "subdomain.example.com/*" zone_name = "example.com" [[routes]] pattern = "subdomain.example.com/*" zone_id = "" ``` Add the `zone_name` or `zone_id` option after each route. The `zone_name` and `zone_id` options are interchangeable. If using `zone_id`, find your zone ID by: 1. Go to the Zone Overview page in the Cloudflare dashboard. [ Go to **Overview** ](https://dash.cloudflare.com/?to=/:account/:zone/) 2. Find the **Zone ID** in the left-hand side of **Overview**. To add multiple routes: * [ wrangler.jsonc ](#tab-panel-7271) * [ wrangler.toml ](#tab-panel-7272) JSONC ``` { "routes": [ { "pattern": "subdomain.example.com/*", "zone_name": "example.com" }, { "pattern": "subdomain-two.example.com/example", "zone_id": "" } ] } ``` Explain Code TOML ``` [[routes]] pattern = "subdomain.example.com/*" zone_name = "example.com" [[routes]] pattern = "subdomain-two.example.com/example" zone_id = "" ``` ## Matching behavior Route patterns look like this: ``` https://*.example.com/images/* ``` This pattern would match all HTTPS requests destined for a subhost of example.com and whose paths are prefixed by `/images/`. A pattern to match all requests looks like this: ``` *example.com/* ``` While they look similar to a [regex ↗](https://en.wikipedia.org/wiki/Regular%5Fexpression) pattern, route patterns follow specific rules: * The only supported operator is the wildcard (`*`), which matches zero or more of any character. * Route patterns may not contain infix wildcards or query parameters. For example, neither `example.com/*.jpg` nor `example.com/?foo=*` are valid route patterns. * When more than one route pattern could match a request URL, the most specific route pattern wins. For example, the pattern `www.example.com/*` would take precedence over `*.example.com/*` when matching a request for `https://www.example.com/`. The pattern `example.com/hello/*` would take precedence over `example.com/*` when matching a request for `example.com/hello/world`. * Route pattern matching considers the entire request URL, including the query parameter string. Since route patterns may not contain query parameters, the only way to have a route pattern match URLs with query parameters is to terminate it with a wildcard, `*`. * The path component of route patterns is case sensitive, for example, `example.com/Images/*` and `example.com/images/*` are two distinct routes. * For routes created before October 15th, 2023, the host component of route patterns is case sensitive, for example, `example.com/*` and `Example.com/*` are two distinct routes. * For routes created on or after October 15th, 2023, the host component of route patterns is not case sensitive, for example, `example.com/*` and `Example.com/*` are equivalent routes. A route can be specified without being associated with a Worker. This will act to negate any less specific patterns. For example, consider this pair of route patterns, one with a Workers script and one without: ``` *example.com/images/cat.png -> *example.com/images/* -> worker-script ``` In this example, all requests destined for example.com and whose paths are prefixed by `/images/` would be routed to `worker-script`, _except_ for `/images/cat.png`, which would bypass Workers completely. Requests with a path of `/images/cat.png?foo=bar` would be routed to `worker-script`, due to the presence of the query string. ## Validity The following set of rules govern route pattern validity. #### Route patterns must include your zone If your zone is `example.com`, then the simplest possible route pattern you can have is `example.com`, which would match `http://example.com/` and `https://example.com/`, and nothing else. As with a URL, there is an implied path of `/` if you do not specify one. #### Route patterns may not contain any query parameters For example, `https://example.com/?anything` is not a valid route pattern. #### Route patterns may optionally begin with `http://` or `https://` If you omit a scheme in your route pattern, it will match both `http://` and `https://` URLs. If you include `http://` or `https://`, it will only match HTTP or HTTPS requests, respectively. * `https://*.example.com/` matches `https://www.example.com/` but not `http://www.example.com/`. * `*.example.com/` matches both `https://www.example.com/` and `http://www.example.com/`. #### Hostnames may optionally begin with `*` If a route pattern hostname begins with `*`, then it matches the host and all subhosts. If a route pattern hostname begins with `*.`, then it only matches all subhosts. * `*example.com/` matches `https://example.com/` and `https://www.example.com/`. * `*.example.com/` matches `https://www.example.com/` but not `https://example.com/`. Warning Because `*` matches zero or more of **any character** (not just subdomains), `*example.com` will also match hostnames that are not subdomains of `example.com`. If you only want to match `example.com` and its subdomains, use two separate routes (`example.com/*` and `*.example.com/*`) instead. The following examples illustrate the difference between `*example.com/*` and `*.example.com/*`: | Request URL | \*example.com/\* | \*.example.com/\* | | ---------------------------- | ---------------- | ----------------- | | https://example.com/ | Matches | Does not match | | https://www.example.com/path | Matches | Matches | | https://myexample.com/ | Matches | Does not match | | https://not-example.com/ | Does not match | Does not match | #### Paths may optionally end with `*` If a route pattern path ends with `*`, then it matches all suffixes of that path. * `https://example.com/path*` matches `https://example.com/path` and `https://example.com/path2` and `https://example.com/path/readme.txt` Warning There is a well-known bug associated with path matching concerning wildcards (`*`) and forward slashes (`/`) that is documented in [Known issues](https://developers.cloudflare.com/workers/platform/known-issues/). #### Domains and subdomains must have a DNS Record All domains and subdomains must have a [DNS record](https://developers.cloudflare.com/dns/manage-dns-records/how-to/create-dns-records/) to be proxied on Cloudflare and used to invoke a Worker. For example, if you want to put a Worker on `myname.example.com`, and you have added `example.com` to Cloudflare but have not added any DNS records for `myname.example.com`, any request to `myname.example.com` will result in the error `ERR_NAME_NOT_RESOLVED`. Warning If you have previously used the Cloudflare dashboard to add an `AAAA` record for `myname` to `example.com`, pointing to `100::` (the [reserved IPv6 discard prefix ↗](https://tools.ietf.org/html/rfc6666)), Cloudflare recommends creating a [Custom Domain](https://developers.cloudflare.com/workers/configuration/routing/custom-domains/) pointing to your Worker instead. ```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/configuration/","name":"Configuration"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/configuration/routing/","name":"Routes and domains"}},{"@type":"ListItem","position":5,"item":{"@id":"/workers/configuration/routing/routes/","name":"Routes"}}]} ``` --- --- title: workers.dev description: Cloudflare Workers accounts come with a workers.dev subdomain that is configurable in the Cloudflare dashboard. Your workers.dev subdomain allows you getting started quickly by deploying Workers without first onboarding your custom domain to Cloudflare. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/configuration/routing/workers-dev.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # workers.dev Cloudflare Workers accounts come with a `workers.dev` subdomain that is configurable in the Cloudflare dashboard. Your `workers.dev` subdomain allows you getting started quickly by deploying Workers without first onboarding your custom domain to Cloudflare. It's recommended to run production Workers on a [Workers route or custom domain](https://developers.cloudflare.com/workers/configuration/routing/), rather than on your `workers.dev` subdomain. Your `workers.dev` subdomain is treated as a [Free website ↗](https://www.cloudflare.com/plans/) and is intended for personal or hobby projects that aren't business-critical. ## Configure `workers.dev` `workers.dev` subdomains take the format: `.workers.dev`. To change your `workers.dev` subdomain: 1. In the Cloudflare dashboard, go to the **Workers & Pages** page. [ Go to **Workers & Pages** ](https://dash.cloudflare.com/?to=/:account/workers-and-pages) 2. Select **Change** next to **Your subdomain**. All Workers are assigned a `workers.dev` route when they are created or renamed following the syntax `..workers.dev`. The [name](https://developers.cloudflare.com/workers/wrangler/configuration/#inheritable-keys) field in your Worker configuration is used as the subdomain for the deployed Worker. ## Manage access to `workers.dev` When enabled, your `workers.dev` URL is available publicly. You can use [Cloudflare Access](https://developers.cloudflare.com/cloudflare-one/access-controls/policies/) to require visitors to authenticate before accessing preview URLs. You can limit access to yourself, your teammates, your organization, or anyone else you specify in your [access policy](https://developers.cloudflare.com/cloudflare-one/access-controls/policies/). To limit your `workers.dev` URL to authorized emails only: 1. In the Cloudflare dashboard, go to the **Workers & Pages** page. [ Go to **Workers & Pages** ](https://dash.cloudflare.com/?to=/:account/workers-and-pages) 2. In **Overview**, select your Worker. 3. Go to **Settings** \> **Domains & Routes**. 4. For `workers.dev`, click **Enable Cloudflare Access**. 5. Optionally, to configure the Access application, click **Manage Cloudflare Access**. There, you can change the email addresses you want to authorize. View [Access policies](https://developers.cloudflare.com/cloudflare-one/access-controls/policies/#selectors) to learn about configuring alternate rules. 6. [Validate the Access JWT ↗](https://developers.cloudflare.com/cloudflare-one/access-controls/applications/http-apps/authorization-cookie/validating-json/#cloudflare-workers-example) in your Worker script using the audience (`aud`) tag and JWKs URL provided. ## Disabling `workers.dev` ### Disabling `workers.dev` in the dashboard To disable the `workers.dev` route for a Worker: 1. In the Cloudflare dashboard, go to the **Workers & Pages** page. [ Go to **Workers & Pages** ](https://dash.cloudflare.com/?to=/:account/workers-and-pages) 2. In **Overview**, select your Worker. 3. Go to **Settings** \> **Domains & Routes**. 4. On `workers.dev` click "Disable". 5. Confirm you want to disable. ### Disabling `workers.dev` in the Wrangler configuration file To disable the `workers.dev` route for a Worker, include the following in your Worker's [Wrangler configuration file](https://developers.cloudflare.com/workers/wrangler/configuration/): * [ wrangler.jsonc ](#tab-panel-7273) * [ wrangler.toml ](#tab-panel-7274) JSONC ``` { "workers_dev": false } ``` TOML ``` workers_dev = false ``` When you redeploy your Worker with this change, the `workers.dev` route will be disabled. Disabling your `workers.dev` route does not disable Preview URLs. Learn how to [disable Preview URLs](https://developers.cloudflare.com/workers/configuration/previews/#disabling-preview-urls). If you do not specify `workers_dev = false` but add a [routes component](https://developers.cloudflare.com/workers/wrangler/configuration/#routes) to your [Wrangler configuration file](https://developers.cloudflare.com/workers/wrangler/configuration/), the value of `workers_dev` will be inferred as `false` on the next deploy. Warning If you disable your `workers.dev` route in the Cloudflare dashboard but do not update your Worker's Wrangler file with `workers_dev = false`, the `workers.dev` route will be re-enabled the next time you deploy your Worker with Wrangler. ## Limitations When deploying a Worker with a `workers.dev` subdomain enabled, your Worker name must meet the following requirements: * Must be 63 characters or less * Must contain only alphanumeric characters (`a-z`, `A-Z`, `0-9`) and dashes (`-`) * Cannot start or end with a dash (`-`) These restrictions apply because the Worker name is used as a DNS label in your `workers.dev` URL. DNS labels have a maximum length of 63 characters and cannot begin or end with a dash. Note Worker names can be up to 255 characters when not using a `workers.dev` subdomain. If you need a longer name, you can disable `workers.dev` and use [routes](https://developers.cloudflare.com/workers/configuration/routing/routes/) or [custom domains](https://developers.cloudflare.com/workers/configuration/routing/custom-domains/) instead. ## Related resources * [Announcing workers.dev ↗](https://blog.cloudflare.com/announcing-workers-dev) * [Wrangler routes configuration](https://developers.cloudflare.com/workers/wrangler/configuration/#types-of-routes) ```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/configuration/","name":"Configuration"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/configuration/routing/","name":"Routes and domains"}},{"@type":"ListItem","position":5,"item":{"@id":"/workers/configuration/routing/workers-dev/","name":"workers.dev"}}]} ``` --- --- title: Secrets description: Store sensitive information, like API keys and auth tokens, in your Worker. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/configuration/secrets.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Secrets ## Background Secrets are a type of binding that allow you to attach encrypted text values to your Worker. Secrets are used for storing sensitive information like API keys and auth tokens. You can access secrets in your Worker code through: * The [env parameter](https://developers.cloudflare.com/workers/runtime-apis/handlers/fetch/#parameters) passed to your Worker's [fetch event handler](https://developers.cloudflare.com/workers/runtime-apis/handlers/fetch/). * Importing `env` from [cloudflare:workers](https://developers.cloudflare.com/workers/runtime-apis/bindings/#importing-env-as-a-global) to access secrets from anywhere in your code. * [process.env](https://developers.cloudflare.com/workers/configuration/environment-variables) in Workers that have [Node.js compatibility](https://developers.cloudflare.com/workers/runtime-apis/nodejs/) enabled. ## Access your secrets with Workers Secrets can be accessed from Workers as you would any other [environment variables](https://developers.cloudflare.com/workers/configuration/environment-variables/). For instance, given a `DB_CONNECTION_STRING` secret, you can access it in your Worker code through the `env` parameter: index.js ``` import postgres from "postgres"; export default { async fetch(request, env, ctx) { const sql = postgres(env.DB_CONNECTION_STRING); const result = await sql`SELECT * FROM products;`; return new Response(JSON.stringify(result), { headers: { "Content-Type": "application/json" }, }); }, }; ``` Explain Code You can also import `env` from `cloudflare:workers` to access secrets from anywhere in your code, including outside of request handlers: * [ JavaScript ](#tab-panel-7275) * [ TypeScript ](#tab-panel-7276) JavaScript ``` import { env } from "cloudflare:workers"; import postgres from "postgres"; // Initialize the database client at the top level using a secret const sql = postgres(env.DB_CONNECTION_STRING); export default { async fetch(request) { const result = await sql`SELECT * FROM products;`; return new Response(JSON.stringify(result), { headers: { "Content-Type": "application/json" }, }); }, }; ``` Explain Code TypeScript ``` import { env } from "cloudflare:workers"; import postgres from "postgres"; // Initialize the database client at the top level using a secret const sql = postgres(env.DB_CONNECTION_STRING); export default { async fetch(request: Request): Promise { const result = await sql`SELECT * FROM products;`; return new Response(JSON.stringify(result), { headers: { "Content-Type": "application/json" }, }); }, }; ``` Explain Code For more details on accessing `env` globally, refer to [Importing env as a global](https://developers.cloudflare.com/workers/runtime-apis/bindings/#importing-env-as-a-global). Secrets Store (beta) Secrets described on this page are defined and managed on a per-Worker level. If you want to use account-level secrets, refer to [Secrets Store](https://developers.cloudflare.com/secrets-store/). Account-level secrets are configured on your Worker as a [Secrets Store binding](https://developers.cloudflare.com/secrets-store/integrations/workers/). ## Local Development with Secrets Warning Do not use `vars` to store sensitive information in your Worker's Wrangler configuration file. Use secrets instead. Put secrets for use in local development in either a `.dev.vars` file or a `.env` file, in the same directory as the Wrangler configuration file. Note You can use the [secrets configuration property](https://developers.cloudflare.com/workers/wrangler/configuration/#secrets-configuration-property) to declare which secret names your Worker requires. When defined, only the keys listed in `secrets.required` are loaded from `.dev.vars` or `.env`. Additional keys are excluded and missing keys produce a warning. Choose to use either `.dev.vars` or `.env` but not both. If you define a `.dev.vars` file, then values in `.env` files will not be included in the `env` object during local development. These files should be formatted using the [dotenv ↗](https://hexdocs.pm/dotenvy/dotenv-file-format.html) syntax. For example: .dev.vars / .env ``` SECRET_KEY="value" API_TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" ``` Do not commit secrets to git The `.dev.vars` and `.env` files should not committed to git. Add `.dev.vars*` and `.env*` to your project's `.gitignore` file. To set different secrets for each Cloudflare environment, create files named `.dev.vars.` or `.env.`. When you select a Cloudflare environment in your local development, the corresponding environment-specific file will be loaded ahead of the generic `.dev.vars` (or `.env`) file. * When using `.dev.vars.` files, all secrets must be defined per environment. If `.dev.vars.` exists then only this will be loaded; the `.dev.vars` file will not be loaded. * In contrast, all matching `.env` files are loaded and the values are merged. For each variable, the value from the most specific file is used, with the following precedence: * `.env..local` (most specific) * `.env.local` * `.env.` * `.env` (least specific) Controlling `.env` handling It is possible to control how `.env` files are loaded in local development by setting environment variables on the process running the tools. * To disable loading local dev vars from `.env` files without providing a `.dev.vars` file, set the `CLOUDFLARE_LOAD_DEV_VARS_FROM_DOT_ENV` environment variable to `"false"`. * To include every environment variable defined in your system's process environment as a local development variable, ensure there is no `.dev.vars` and then set the `CLOUDFLARE_INCLUDE_PROCESS_ENV` environment variable to `"true"`. This is not needed when using the [secrets configuration property](https://developers.cloudflare.com/workers/wrangler/configuration/#secrets-configuration-property), which loads from `process.env` automatically. ## Secrets on deployed Workers ### Validate secrets before deploy You can declare the secret names your Worker requires using the [secrets configuration property](https://developers.cloudflare.com/workers/wrangler/configuration/#secrets-configuration-property) in your Wrangler configuration. When defined, `wrangler deploy` and `wrangler versions upload` will fail with a clear error if any required secrets are not configured on the Worker. ### Adding secrets to your project #### Via Wrangler Secrets can be added through [wrangler secret put](https://developers.cloudflare.com/workers/wrangler/commands/general/#secret) or [wrangler versions secret put](https://developers.cloudflare.com/workers/wrangler/commands/general/#versions-secret-put) commands. `wrangler secret put` creates a new version of the Worker and deploys it immediately. Terminal window ``` npx wrangler secret put ``` If using [gradual deployments](https://developers.cloudflare.com/workers/configuration/versions-and-deployments/gradual-deployments/), instead use the `wrangler versions secret put` command. This will only create a new version of the Worker, that can then be deploying using [wrangler versions deploy](https://developers.cloudflare.com/workers/wrangler/commands/general/#versions-deploy). Note Wrangler versions before 3.73.0 require you to specify a `--x-versions` flag. Terminal window ``` npx wrangler versions secret put ``` #### Via the dashboard To add a secret via the dashboard: 1. In the Cloudflare dashboard, go to the **Workers & Pages** page. [ Go to **Workers & Pages** ](https://dash.cloudflare.com/?to=/:account/workers-and-pages) 2. In **Overview**, select your Worker > **Settings**. 3. Under **Variables and Secrets**, select **Add**. 4. Select the type **Secret**, input a **Variable name**, and input its **Value**. This secret will be made available to your Worker but the value will be hidden in Wrangler and the dashboard. 5. (Optional) To add more secrets, select **Add variable**. 6. Select **Deploy** to implement your changes. ### Delete secrets from your project #### Via Wrangler Secrets can be deleted through [wrangler secret delete](https://developers.cloudflare.com/workers/wrangler/commands/general/#secret-delete) or [wrangler versions secret delete](https://developers.cloudflare.com/workers/wrangler/commands/general/#versions-secret-delete) commands. `wrangler secret delete` creates a new version of the Worker and deploys it immediately. Terminal window ``` npx wrangler secret delete ``` If using [gradual deployments](https://developers.cloudflare.com/workers/configuration/versions-and-deployments/gradual-deployments/), instead use the `wrangler versions secret delete` command. This will only create a new version of the Worker, that can then be deploying using [wrangler versions deploy](https://developers.cloudflare.com/workers/wrangler/commands/general/#versions-deploy). Terminal window ``` npx wrangler versions secret delete ``` #### Via the dashboard To delete a secret from your Worker project via the dashboard: 1. In the Cloudflare dashboard, go to the **Workers & Pages** page. [ Go to **Workers & Pages** ](https://dash.cloudflare.com/?to=/:account/workers-and-pages) 2. In **Overview**, select your Worker > **Settings**. 3. Under **Variables and Secrets**, select **Edit**. 4. In the **Edit** drawer, select **X** next to the secret you want to delete. 5. Select **Deploy** to implement your changes. 6. (Optional) Instead of using the edit drawer, you can click the delete icon next to the secret. ## Compare secrets and environment variables Use secrets for sensitive information Do not use plaintext environment variables to store sensitive information. Use [secrets](https://developers.cloudflare.com/workers/configuration/secrets/) or [Secrets Store bindings](https://developers.cloudflare.com/secrets-store/integrations/workers/) instead. [Secrets](https://developers.cloudflare.com/workers/configuration/secrets/) are [environment variables](https://developers.cloudflare.com/workers/configuration/environment-variables/). The difference is secret values are not visible within Wrangler or Cloudflare dashboard after you define them. This means that sensitive data, including passwords or API tokens, should always be encrypted to prevent data leaks. To your Worker, there is no difference between an environment variable and a secret. The secret's value is passed through as defined. ## Related resources * [Wrangler secret commands](https://developers.cloudflare.com/workers/wrangler/commands/general/#secret) \- Review the Wrangler commands to create, delete and list secrets. * [secrets configuration property](https://developers.cloudflare.com/workers/wrangler/configuration/#secrets-configuration-property) \- Declare required secret names in your Wrangler configuration. Used for validation during local development and deploy, and as the source of truth for type generation. * [Cloudflare Secrets Store](https://developers.cloudflare.com/secrets-store/) \- Encrypt and store sensitive information as secrets that are securely reusable across your account. ```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/configuration/","name":"Configuration"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/configuration/secrets/","name":"Secrets"}}]} ``` --- --- title: Workers Sites description: Use [Workers Static Assets](/workers/static-assets/) to host full-stack applications instead of Workers Sites. Do not use Workers Sites for new projects. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/configuration/sites/index.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Workers Sites Use Workers Static Assets Instead You should use [Workers Static Assets](https://developers.cloudflare.com/workers/static-assets/) to host full-stack applications instead of Workers Sites. It has been deprecated in Wrangler v4, and the [Cloudflare Vite plugin](https://developers.cloudflare.com/workers/vite-plugin/) does not support Workers Sites. Do not use Workers Sites for new projects. Workers Sites enables developers to deploy static applications directly to Workers. It can be used for deploying applications built with static site generators like [Hugo ↗](https://gohugo.io) and [Gatsby ↗](https://www.gatsbyjs.org), or front-end frameworks like [Vue ↗](https://vuejs.org) and [React ↗](https://reactjs.org). To deploy with Workers Sites, select from one of these three approaches depending on the state of your target project: --- ## 1\. Start from scratch If you are ready to start a brand new project, this quick start guide will help you set up the infrastructure to deploy a HTML website to Workers. [ Start from scratch ](https://developers.cloudflare.com/workers/configuration/sites/start-from-scratch/) --- ## 2\. Deploy an existing static site If you have an existing project or static assets that you want to deploy with Workers, this quick start guide will help you install Wrangler and configure Workers Sites for your project. [ Start from an existing static site ](https://developers.cloudflare.com/workers/configuration/sites/start-from-existing/) --- ## 3\. Add static assets to an existing Workers project If you already have a Worker deployed to Cloudflare, this quick start guide will show you how to configure the existing codebase to use Workers Sites. [ Start from an existing Worker ](https://developers.cloudflare.com/workers/configuration/sites/start-from-worker/) Note Workers Sites is built on Workers KV, and usage rates may apply. Refer to [Pricing](https://developers.cloudflare.com/workers/platform/pricing/) to learn more. ```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/configuration/","name":"Configuration"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/configuration/sites/","name":"Workers Sites"}}]} ``` --- --- title: Workers Sites configuration description: Workers Sites require the latest version of Wrangler. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/configuration/sites/configuration.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Workers Sites configuration Use Workers Static Assets Instead You should use [Workers Static Assets](https://developers.cloudflare.com/workers/static-assets/) to host full-stack applications instead of Workers Sites. It has been deprecated in Wrangler v4, and the [Cloudflare Vite plugin](https://developers.cloudflare.com/workers/vite-plugin/) does not support Workers Sites. Do not use Workers Sites for new projects. Workers Sites require the latest version of [Wrangler ↗](https://github.com/cloudflare/workers-sdk/tree/main/packages/wrangler). ## Wrangler configuration file There are a few specific configuration settings for Workers Sites in your Wrangler file: * `bucket` required * The directory containing your static assets, path relative to your [Wrangler configuration file](https://developers.cloudflare.com/workers/wrangler/configuration/). Example: `bucket = "./public"`. * `include` optional * A list of gitignore-style patterns for files or directories in `bucket` you exclusively want to upload. Example: `include = ["upload_dir"]`. * `exclude` optional * A list of gitignore-style patterns for files or directories in `bucket` you want to exclude from uploads. Example: `exclude = ["ignore_dir"]`. To learn more about the optional `include` and `exclude` fields, refer to [Ignoring subsets of static assets](#ignoring-subsets-of-static-assets). Note If your project uses [environments](https://developers.cloudflare.com/workers/wrangler/environments/), make sure to place `site` above any environment-specific configuration blocks. Example of a [Wrangler configuration file](https://developers.cloudflare.com/workers/wrangler/configuration/): * [ wrangler.jsonc ](#tab-panel-7281) * [ wrangler.toml ](#tab-panel-7282) JSONC ``` { "$schema": "./node_modules/wrangler/config-schema.json", "name": "docs-site-blah", "site": { "bucket": "./public" }, "env": { "production": { "name": "docs-site", "route": "https://example.com/docs*" }, "staging": { "name": "docs-site-staging", "route": "https://staging.example.com/docs*" } } } ``` Explain Code TOML ``` "$schema" = "./node_modules/wrangler/config-schema.json" name = "docs-site-blah" [site] bucket = "./public" [env.production] name = "docs-site" route = "https://example.com/docs*" [env.staging] name = "docs-site-staging" route = "https://staging.example.com/docs*" ``` Explain Code ## Storage limits For very exceptionally large pages, Workers Sites might not work for you. There is a 25 MiB limit per page or file. ## Ignoring subsets of static assets Workers Sites require [Wrangler ↗](https://github.com/cloudflare/workers-sdk/tree/main/packages/wrangler) \- make sure to use the [latest version](https://developers.cloudflare.com/workers/wrangler/install-and-update/#update-wrangler). There are cases where users may not want to upload certain static assets to their Workers Sites. In this case, Workers Sites can also be configured to ignore certain files or directories using logic similar to [Cargo's optional include and exclude fields ↗](https://doc.rust-lang.org/cargo/reference/manifest.html#the-exclude-and-include-fields-optional). This means that you should use gitignore semantics when declaring which directory entries to include or ignore in uploads. ### Exclusively including files/directories If you want to include only a certain set of files or directories in your `bucket`, you can add an `include` field to your `[site]` section of your Wrangler file: * [ wrangler.jsonc ](#tab-panel-7277) * [ wrangler.toml ](#tab-panel-7278) JSONC ``` { "site": { "bucket": "./public", "include": [ // must be an array. "included_dir" ] } } ``` TOML ``` [site] bucket = "./public" include = [ "included_dir" ] ``` Wrangler will only upload files or directories matching the patterns in the `include` array. ### Excluding files/directories If you want to exclude files or directories in your `bucket`, you can add an `exclude` field to your `[site]` section of your Wrangler file: * [ wrangler.jsonc ](#tab-panel-7279) * [ wrangler.toml ](#tab-panel-7280) JSONC ``` { "site": { "bucket": "./public", "exclude": [ // must be an array. "excluded_dir" ] } } ``` TOML ``` [site] bucket = "./public" exclude = [ "excluded_dir" ] ``` Wrangler will ignore files or directories matching the patterns in the `exclude` array when uploading assets to Workers KV. ### Include > exclude If you provide both `include` and `exclude` fields, the `include` field will be used and the `exclude` field will be ignored. ### Default ignored entries Wrangler will always ignore: * `node_modules` * Hidden files and directories * Symlinks #### More about include/exclude patterns Learn more about the standard patterns used for include and exclude in the [gitignore documentation ↗](https://git-scm.com/docs/gitignore). ```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/configuration/","name":"Configuration"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/configuration/sites/","name":"Workers Sites"}},{"@type":"ListItem","position":5,"item":{"@id":"/workers/configuration/sites/configuration/","name":"Workers Sites configuration"}}]} ``` --- --- title: Start from existing description: Workers Sites require Wrangler — make sure to use the latest version. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/configuration/sites/start-from-existing.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Start from existing Use Workers Static Assets Instead You should use [Workers Static Assets](https://developers.cloudflare.com/workers/static-assets/) to host full-stack applications instead of Workers Sites. It has been deprecated in Wrangler v4, and the [Cloudflare Vite plugin](https://developers.cloudflare.com/workers/vite-plugin/) does not support Workers Sites. Do not use Workers Sites for new projects. Workers Sites require [Wrangler ↗](https://github.com/cloudflare/workers-sdk/tree/main/packages/wrangler) — make sure to use the [latest version](https://developers.cloudflare.com/workers/wrangler/install-and-update/#update-wrangler). To deploy a pre-existing static site project, start with a pre-generated site. Workers Sites works with all static site generators, for example: * [Hugo ↗](https://gohugo.io/getting-started/quick-start/) * [Gatsby ↗](https://www.gatsbyjs.org/docs/quick-start/), requires Node * [Jekyll ↗](https://jekyllrb.com/docs/), requires Ruby * [Eleventy ↗](https://www.11ty.io/#quick-start), requires Node * [WordPress ↗](https://wordpress.org) (refer to the tutorial on [deploying static WordPress sites with Pages](https://developers.cloudflare.com/pages/how-to/deploy-a-wordpress-site/)) ## Getting started 1. Run the `wrangler init` command in the root of your project's directory to generate a basic Worker: Terminal window ``` wrangler init -y ``` This command adds/update the following files: * `wrangler.jsonc`: The file containing project configuration. * `package.json`: Wrangler `devDependencies` are added. * `tsconfig.json`: Added if not already there to support writing the Worker in TypeScript. * `src/index.ts`: A basic Cloudflare Worker, written in TypeScript. 2. Add your site's build/output directory to the Wrangler file: * [ wrangler.jsonc ](#tab-panel-7285) * [ wrangler.toml ](#tab-panel-7286) JSONC ``` { "site": { "bucket": "./public" // <-- Add your build directory name here. } } ``` TOML ``` [site] bucket = "./public" ``` The default directories for the most popular static site generators are listed below: * Hugo: `public` * Gatsby: `public` * Jekyll: `_site` * Eleventy: `_site` 3. Install the `@cloudflare/kv-asset-handler` package in your project: Terminal window ``` npm i -D @cloudflare/kv-asset-handler ``` 4. Replace the contents of `src/index.ts` with the following code snippet: * [ Module Worker ](#tab-panel-7283) * [ Service Worker ](#tab-panel-7284) JavaScript ``` import { getAssetFromKV } from "@cloudflare/kv-asset-handler"; import manifestJSON from "__STATIC_CONTENT_MANIFEST"; const assetManifest = JSON.parse(manifestJSON); export default { async fetch(request, env, ctx) { try { // Add logic to decide whether to serve an asset or run your original Worker code return await getAssetFromKV( { request, waitUntil: ctx.waitUntil.bind(ctx), }, { ASSET_NAMESPACE: env.__STATIC_CONTENT, ASSET_MANIFEST: assetManifest, }, ); } catch (e) { let pathname = new URL(request.url).pathname; return new Response(`"${pathname}" not found`, { status: 404, statusText: "not found", }); } }, }; ``` Explain Code Service Workers are deprecated Service Workers are deprecated, but still supported. We recommend using [Module Workers](https://developers.cloudflare.com/workers/reference/migrate-to-module-workers/) instead. New features may not be supported for Service Workers. JavaScript ``` import { getAssetFromKV } from "@cloudflare/kv-asset-handler"; addEventListener("fetch", (event) => { event.respondWith(handleEvent(event)); }); async function handleEvent(event) { try { // Add logic to decide whether to serve an asset or run your original Worker code return await getAssetFromKV(event); } catch (e) { let pathname = new URL(event.request.url).pathname; return new Response(`"${pathname}" not found`, { status: 404, statusText: "not found", }); } } ``` Explain Code 1. Run `wrangler dev` or `npx wrangler deploy` to preview or deploy your site on Cloudflare. Wrangler will automatically upload the assets found in the configured directory. Terminal window ``` npx wrangler deploy ``` 2. Deploy your site to a [custom domain](https://developers.cloudflare.com/workers/configuration/routing/custom-domains/) that you own and have already attached as a Cloudflare zone. Add a `route` property to the Wrangler file. * [ wrangler.jsonc ](#tab-panel-7287) * [ wrangler.toml ](#tab-panel-7288) JSONC ``` { "route": "https://example.com/*" } ``` TOML ``` route = "https://example.com/*" ``` Note Refer to the documentation on [Routes](https://developers.cloudflare.com/workers/configuration/routing/routes/) to configure a `route` properly. Learn more about [configuring your project](https://developers.cloudflare.com/workers/wrangler/configuration/). ```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/configuration/","name":"Configuration"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/configuration/sites/","name":"Workers Sites"}},{"@type":"ListItem","position":5,"item":{"@id":"/workers/configuration/sites/start-from-existing/","name":"Start from existing"}}]} ``` --- --- title: Start from scratch description: This guide shows how to quickly start a new Workers Sites project from scratch. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/configuration/sites/start-from-scratch.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Start from scratch Use Workers Static Assets Instead You should use [Workers Static Assets](https://developers.cloudflare.com/workers/static-assets/) to host full-stack applications instead of Workers Sites. It has been deprecated in Wrangler v4, and the [Cloudflare Vite plugin](https://developers.cloudflare.com/workers/vite-plugin/) does not support Workers Sites. Do not use Workers Sites for new projects. This guide shows how to quickly start a new Workers Sites project from scratch. ## Getting started 1. Ensure you have the latest version of [git ↗](https://git-scm.com/downloads) and [Node.js ↗](https://nodejs.org/en/download/) installed. 2. In your terminal, clone the `worker-sites-template` starter repository. The following example creates a project called `my-site`: Terminal window ``` git clone --depth=1 --branch=wrangler2 https://github.com/cloudflare/worker-sites-template my-site ``` 3. Run `npm install` to install all dependencies. 4. You can preview your site by running the [wrangler dev](https://developers.cloudflare.com/workers/wrangler/commands/general/#dev) command: Terminal window ``` wrangler dev ``` 5. Deploy your site to Cloudflare: Terminal window ``` npx wrangler deploy ``` ## Project layout The template project contains the following files and directories: * `public`: The static assets for your project. By default it contains an `index.html` and a `favicon.ico`. * `src`: The Worker configured for serving your assets. You do not need to edit this but if you want to see how it works or add more functionality to your Worker, you can edit `src/index.ts`. * `wrangler.jsonc`: The file containing project configuration. The `bucket` property tells Wrangler where to find the static assets (e.g. `site = { bucket = "./public" }`). * `package.json`/`package-lock.json`: define the required Node.js dependencies. ## Customize the `wrangler.jsonc` file: * Change the `name` property to the name of your project: * [ wrangler.jsonc ](#tab-panel-7289) * [ wrangler.toml ](#tab-panel-7290) JSONC ``` { "$schema": "./node_modules/wrangler/config-schema.json", "name": "my-site" } ``` TOML ``` "$schema" = "./node_modules/wrangler/config-schema.json" name = "my-site" ``` * Consider updating`compatibility_date` to today's date to get access to the most recent Workers features: * [ wrangler.jsonc ](#tab-panel-7291) * [ wrangler.toml ](#tab-panel-7292) JSONC ``` { "compatibility_date": "yyyy-mm-dd" } ``` TOML ``` compatibility_date = "yyyy-mm-dd" ``` * Deploy your site to a [custom domain](https://developers.cloudflare.com/workers/configuration/routing/custom-domains/) that you own and have already attached as a Cloudflare zone: * [ wrangler.jsonc ](#tab-panel-7293) * [ wrangler.toml ](#tab-panel-7294) JSONC ``` { "route": "https://example.com/*" } ``` TOML ``` route = "https://example.com/*" ``` Note Refer to the documentation on [Routes](https://developers.cloudflare.com/workers/configuration/routing/routes/) to configure a `route` properly. Learn more about [configuring your project](https://developers.cloudflare.com/workers/wrangler/configuration/). ```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/configuration/","name":"Configuration"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/configuration/sites/","name":"Workers Sites"}},{"@type":"ListItem","position":5,"item":{"@id":"/workers/configuration/sites/start-from-scratch/","name":"Start from scratch"}}]} ``` --- --- title: Start from Worker description: Workers Sites require Wrangler — make sure to use the latest version. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/configuration/sites/start-from-worker.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Start from Worker Use Workers Static Assets Instead You should use [Workers Static Assets](https://developers.cloudflare.com/workers/static-assets/) to host full-stack applications instead of Workers Sites. It has been deprecated in Wrangler v4, and the [Cloudflare Vite plugin](https://developers.cloudflare.com/workers/vite-plugin/) does not support Workers Sites. Do not use Workers Sites for new projects. Workers Sites require [Wrangler ↗](https://github.com/cloudflare/workers-sdk/tree/main/packages/wrangler) — make sure to use the [latest version](https://developers.cloudflare.com/workers/wrangler/install-and-update/#update-wrangler). If you have a pre-existing Worker project, you can use Workers Sites to serve static assets to the Worker. ## Getting started 1. Create a directory that will contain the assets in the root of your project (for example, `./public`) 2. Add configuration to your Wrangler file to point to it. * [ wrangler.jsonc ](#tab-panel-7297) * [ wrangler.toml ](#tab-panel-7298) JSONC ``` { "site": { "bucket": "./public" // Add the directory with your static assets! } } ``` TOML ``` [site] bucket = "./public" ``` 3. Install the `@cloudflare/kv-asset-handler` package in your project: Terminal window ``` npm i -D @cloudflare/kv-asset-handler ``` 4. Import the `getAssetFromKV()` function into your Worker entry point and use it to respond with static assets. * [ Module Worker ](#tab-panel-7295) * [ Service Worker ](#tab-panel-7296) JavaScript ``` import { getAssetFromKV } from "@cloudflare/kv-asset-handler"; import manifestJSON from "__STATIC_CONTENT_MANIFEST"; const assetManifest = JSON.parse(manifestJSON); export default { async fetch(request, env, ctx) { try { // Add logic to decide whether to serve an asset or run your original Worker code return await getAssetFromKV( { request, waitUntil: ctx.waitUntil.bind(ctx), }, { ASSET_NAMESPACE: env.__STATIC_CONTENT, ASSET_MANIFEST: assetManifest, }, ); } catch (e) { let pathname = new URL(request.url).pathname; return new Response(`"${pathname}" not found`, { status: 404, statusText: "not found", }); } }, }; ``` Explain Code JavaScript ``` import { getAssetFromKV } from "@cloudflare/kv-asset-handler"; addEventListener("fetch", (event) => { event.respondWith(handleEvent(event)); }); async function handleEvent(event) { try { // Add logic to decide whether to serve an asset or run your original Worker code return await getAssetFromKV(event); } catch (e) { let pathname = new URL(event.request.url).pathname; return new Response(`"${pathname}" not found`, { status: 404, statusText: "not found", }); } } ``` Explain Code For more information on the configurable options of `getAssetFromKV()` refer to [kv-asset-handler docs ↗](https://github.com/cloudflare/workers-sdk/tree/main/packages/kv-asset-handler). 1. Run `wrangler deploy` or `npx wrangler deploy` as you would normally with your Worker project. Wrangler will automatically upload the assets found in the configured directory. Terminal window ``` npx wrangler deploy ``` ```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/configuration/","name":"Configuration"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/configuration/sites/","name":"Workers Sites"}},{"@type":"ListItem","position":5,"item":{"@id":"/workers/configuration/sites/start-from-worker/","name":"Start from Worker"}}]} ``` --- --- title: Versions & Deployments description: Upload versions of Workers and create deployments to release new versions. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/configuration/versions-and-deployments/index.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Versions & Deployments Versions track changes to your Worker. Deployments configure how those changes are deployed to your traffic. You can upload changes (versions) to your Worker independent of changing the version that is actively serving traffic (deployment). ![Versions and Deployments](https://developers.cloudflare.com/_astro/versions-and-deployments.Dnwtp7bX_1XrgKm.webp) Using versions and deployments is useful if: * You are running critical applications on Workers and want to reduce risk when deploying new versions of your Worker using a rolling deployment strategy. * You want to monitor for performance differences when deploying new versions of your Worker. * You have a CI/CD pipeline configured for Workers but want to cut manual releases. ## Versions A version is defined by the state of code as well as the state of configuration in a Worker's [Wrangler configuration file](https://developers.cloudflare.com/workers/wrangler/configuration/). Versions track historical changes to [bundled code](https://developers.cloudflare.com/workers/wrangler/bundling/), [static assets](https://developers.cloudflare.com/workers/static-assets/) and changes to configuration like [bindings](https://developers.cloudflare.com/workers/runtime-apis/bindings/) and [compatibility date and compatibility flags](https://developers.cloudflare.com/workers/configuration/compatibility-dates/) over time. Versions also track metadata associated with a version, including: the version ID, the user that created the version, deploy source, and timestamp. Optionally, a version message and version tag can be configured on version upload. Note State changes for associated Workers [storage resources](https://developers.cloudflare.com/workers/platform/storage-options/) such as [KV](https://developers.cloudflare.com/kv/), [R2](https://developers.cloudflare.com/r2/), [Durable Objects](https://developers.cloudflare.com/durable-objects/) and [D1](https://developers.cloudflare.com/d1/) are not tracked with versions. ## Deployments Deployments track the version(s) of your Worker that are actively serving traffic. A deployment can consist of one or two versions of a Worker. By default, Workers supports an all-at-once deployment model where traffic is immediately shifted from one version to the newly deployed version automatically. Alternatively, you can use [gradual deployments](https://developers.cloudflare.com/workers/configuration/versions-and-deployments/gradual-deployments/) to create a rolling deployment strategy. You can also track metadata associated with a deployment, including: the user that created the deployment, deploy source, timestamp and the version(s) in the deployment. Optionally, you can configure a deployment message when you create a deployment. ## Use versions and deployments ### Create a new version Review the different ways you can create versions of your Worker and deploy them. #### Upload a new version and deploy it immediately A new version that is automatically deployed to 100% of traffic when: * Changes are uploaded with [wrangler deploy](https://developers.cloudflare.com/workers/wrangler/commands/general/#deploy) via the Cloudflare Dashboard * Changes are deployed with the command [npx wrangler deploy](https://developers.cloudflare.com/workers/wrangler/commands/general/#deploy) via [Workers Builds](https://developers.cloudflare.com/workers/ci-cd/builds) * Changes are uploaded with the [Workers Script Upload API](https://developers.cloudflare.com/api/resources/workers/subresources/scripts/methods/update/) #### Upload a new version to be gradually deployed or deployed at a later time Note Wrangler versions before 3.73.0 require you to specify a `--x-versions` flag. To create a new version of your Worker that is not deployed immediately, use the [wrangler versions upload](https://developers.cloudflare.com/workers/wrangler/commands/general/#versions-upload) command or create a new version via the Cloudflare dashboard using the **Save** button. You can find the **Save** option under the down arrow beside the "Deploy" button. Versions created in this way can then be deployed all at once or gradually deployed using the [wrangler versions deploy](https://developers.cloudflare.com/workers/wrangler/commands/general/#versions-deploy) command or via the Cloudflare dashboard under the **Deployments** tab. Note When using [Wrangler](https://developers.cloudflare.com/workers/wrangler/), changes made to a Worker's triggers [routes, domains](https://developers.cloudflare.com/workers/configuration/routing/) or [cron triggers](https://developers.cloudflare.com/workers/configuration/cron-triggers/) need to be applied with the command [wrangler triggers deploy](https://developers.cloudflare.com/workers/wrangler/commands/general/#triggers). Note New versions are not created when you make changes to [resources connected to your Worker](https://developers.cloudflare.com/workers/runtime-apis/bindings/). For example, if two Workers (Worker A and Worker B) are connected via a [service binding](https://developers.cloudflare.com/workers/runtime-apis/bindings/service-bindings/), changing the code of Worker B will not create a new version of Worker A. Changing the code of Worker B will only create a new version of Worker B. Changes to the service binding (such as, deleting the binding or updating the [environment](https://developers.cloudflare.com/workers/wrangler/environments/) it points to) on Worker A will also not create a new version of Worker B. #### Directly manage Versions and Deployments See examples of creating a Worker, Versions, and Deployments directly with the API, library SDKs, and Terraform in [Infrastructure as Code](https://developers.cloudflare.com/workers/platform/infrastructure-as-code/). ### View versions and deployments #### Via Wrangler Wrangler allows you to view the 100 most recent versions and deployments. Refer to the [versions list](https://developers.cloudflare.com/workers/wrangler/commands/general/#list-4) and [deployments](https://developers.cloudflare.com/workers/wrangler/commands/general/#list-5) documentation to view the commands. #### Via the Cloudflare dashboard To view your deployments in the Cloudflare dashboard: 1. In the Cloudflare dashboard, go to the **Workers & Pages** page. [ Go to **Workers & Pages** ](https://dash.cloudflare.com/?to=/:account/workers-and-pages) 2. Select your Worker > **Deployments**. ## Limits ### First upload You must use [C3](https://developers.cloudflare.com/workers/get-started/guide/#1-create-a-new-worker-project) or [wrangler deploy](https://developers.cloudflare.com/workers/wrangler/commands/general/#deploy) the first time you create a new Workers project. Using [wrangler versions upload](https://developers.cloudflare.com/workers/wrangler/commands/general/#versions-upload) the first time you upload a Worker will fail. ### Service worker syntax Service worker syntax is not supported for versions that are uploaded through [wrangler versions upload](https://developers.cloudflare.com/workers/wrangler/commands/general/#versions-upload). You must use ES modules format. Refer to [Migrate from Service Workers to ES modules](https://developers.cloudflare.com/workers/reference/migrate-to-module-workers/#advantages-of-migrating) to learn how to migrate your Workers from the service worker format to the ES modules format. ### Durable Object migrations Uploading a version with [Durable Object migrations](https://developers.cloudflare.com/durable-objects/reference/durable-objects-migrations/) is not supported. Use [wrangler deploy](https://developers.cloudflare.com/workers/wrangler/commands/general/#deploy) if you are applying a [Durable Object migration](https://developers.cloudflare.com/durable-objects/reference/durable-objects-migrations/). This will be supported in the near future. ```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/configuration/","name":"Configuration"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/configuration/versions-and-deployments/","name":"Versions & Deployments"}}]} ``` --- --- title: Gradual deployments description: Incrementally deploy code changes to your Workers with gradual deployments. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/configuration/versions-and-deployments/gradual-deployments.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Gradual deployments Gradual Deployments give you the ability to incrementally deploy new [versions](https://developers.cloudflare.com/workers/configuration/versions-and-deployments/#versions) of Workers by splitting traffic across versions. ![Gradual Deployments](https://developers.cloudflare.com/_astro/gradual-deployments.C6F9MQ6U_ZVKcdL.webp) Using gradual deployments, you can: * Gradually shift traffic to a newer version of your Worker. * Monitor error rates and exceptions across versions using [analytics and logs](https://developers.cloudflare.com/workers/configuration/versions-and-deployments/gradual-deployments/#observability) tooling. * [Roll back](https://developers.cloudflare.com/workers/configuration/versions-and-deployments/rollbacks/) to a previously stable version if you notice issues when deploying a new version. ## Use gradual deployments The following section guides you through an example usage of gradual deployments. You will choose to use either [Wrangler](https://developers.cloudflare.com/workers/configuration/versions-and-deployments/gradual-deployments/#via-wrangler) or the Cloudflare dashboard to: * Create a new Worker. * Publish a new version of that Worker without deploying it. * Create a gradual deployment between the two versions. * Progress the deployment of the new version to 100% of traffic. ### Via Wrangler Note Minimum required Wrangler version: 3.40.0\. Versions before 3.73.0 require you to specify a `--x-versions` flag. #### 1\. Create and deploy a new Worker Create a new `"Hello World"` Worker using the [create-cloudflare CLI (C3)](https://developers.cloudflare.com/pages/get-started/c3/) and deploy it. npm yarn pnpm ``` npm create cloudflare@latest -- -- --type=hello-world ``` ``` yarn create cloudflare -- --type=hello-world ``` ``` pnpm create cloudflare@latest -- --type=hello-world ``` Answer `yes` or `no` to using TypeScript. Answer `yes` to deploying your application. This is the first version of your Worker. #### 2\. Create a new version of the Worker To create a new version of the Worker, edit the Worker code by changing the `Response` content to your desired text and upload the Worker by using the [wrangler versions upload](https://developers.cloudflare.com/workers/wrangler/commands/general/#versions-upload) command. npm yarn pnpm ``` npx wrangler versions upload ``` ``` yarn wrangler versions upload ``` ``` pnpm wrangler versions upload ``` This will create a new version of the Worker that is not automatically deployed. #### 3\. Create a new deployment Use the [wrangler versions deploy](https://developers.cloudflare.com/workers/wrangler/commands/general/#versions-deploy) command to create a new deployment that splits traffic between two versions of the Worker. Follow the interactive prompts to create a deployment with the versions uploaded in [step #1](https://developers.cloudflare.com/workers/configuration/versions-and-deployments/gradual-deployments/#1-create-and-deploy-a-new-worker) and [step #2](https://developers.cloudflare.com/workers/configuration/versions-and-deployments/gradual-deployments/#2-create-a-new-version-of-the-worker). Select your desired percentages for each version. npm yarn pnpm ``` npx wrangler versions deploy ``` ``` yarn wrangler versions deploy ``` ``` pnpm wrangler versions deploy ``` #### 4\. Test the split deployment Run a cURL command on your Worker to test the split deployment. Terminal window ``` for j in {0..10} do curl -s https://$WORKER_NAME.$SUBDOMAIN.workers.dev done ``` You should see 10 responses. Responses will reflect the content returned by the versions in your deployment. Responses will vary depending on the percentages configured in [step #3](https://developers.cloudflare.com/workers/configuration/versions-and-deployments/gradual-deployments/#3-create-a-new-deployment). You can test also target a specific version using [version overrides](#version-overrides). #### 5\. Set your new version to 100% deployment Run `wrangler versions deploy` again and follow the interactive prompts. Select the version uploaded in [step 2](https://developers.cloudflare.com/workers/configuration/versions-and-deployments/gradual-deployments/#2-create-a-new-version-of-the-worker) and set it to 100% deployment. npm yarn pnpm ``` npx wrangler versions deploy ``` ``` yarn wrangler versions deploy ``` ``` pnpm wrangler versions deploy ``` ### Via the Cloudflare dashboard 1. In the Cloudflare dashboard, go to the **Workers & Pages** page. [ Go to **Workers & Pages** ](https://dash.cloudflare.com/?to=/:account/workers-and-pages) 2. Select **Create application** \> **Hello World** template > deploy your Worker. 3. Once the Worker is deployed, go to the online code editor through **Edit code**. Edit the Worker code (change the `Response` content) and upload the Worker. 4. To save changes, select the **down arrow** next to **Deploy** \> **Save**. This will create a new version of your Worker. 5. Create a new deployment that splits traffic between the two versions created in step 3 and 5 by going to **Deployments** and selecting **Deploy Version**. 6. cURL your Worker to test the split deployment. Terminal window ``` for j in {0..10} do curl -s https://$WORKER_NAME.$SUBDOMAIN.workers.dev done ``` You should see 10 responses. Responses will reflect the content returned by the versions in your deployment. Responses will vary depending on the percentages configured in step #6. ## Gradual deployments with static assets When your Worker serves [static assets](https://developers.cloudflare.com/workers/static-assets/), gradual deployments can cause asset compatibility issues where users receive HTML from one version that references assets only available in another version, leading to 404 errors. For detailed guidance on handling static assets during gradual rollouts, including specific examples and configuration steps, refer to [Gradual rollouts](https://developers.cloudflare.com/workers/static-assets/routing/advanced/gradual-rollouts/). ## Version affinity By default, the percentages configured when using gradual deployments operate on a per-request basis — a request has a X% probability of invoking one of two versions of the Worker in the [deployment](https://developers.cloudflare.com/workers/configuration/versions-and-deployments/#deployments). You may want requests associated with a particular identifier (such as user, session, or any unique ID) to be handled by a consistent version of your Worker to prevent version skew. Version skew occurs when there are multiple versions of an application deployed that are not forwards/backwards compatible. You can configure version affinity to prevent the Worker's version from changing back and forth on a per-request basis. You can do this by setting the `Cloudflare-Workers-Version-Key` header on the incoming request to your Worker. For example: Terminal window ``` curl -s https://example.com -H 'Cloudflare-Workers-Version-Key: foo' ``` For a given [deployment](https://developers.cloudflare.com/workers/configuration/versions-and-deployments/#deployments), all requests with a version key set to `foo` will be handled by the same version of your Worker. The specific version of your Worker that the version key `foo` corresponds to is determined by the percentages you have configured for each Worker version in your deployment. You can set the `Cloudflare-Workers-Version-Key` header both when making an external request from the Internet to your Worker, as well as when making a subrequest from one Worker to another Worker using a [service binding](https://developers.cloudflare.com/workers/runtime-apis/bindings/service-bindings/). ### Setting `Cloudflare-Workers-Version-Key` using Ruleset Engine You may want to extract a version key from certain properties of your request such as the URL, headers or cookies. You can configure a [Ruleset Engine](https://developers.cloudflare.com/ruleset-engine/) rule on your zone to do this. This allows you to specify version affinity based on these properties without having to modify the external client that makes the request. For example, if your worker serves video assets under the URI path `/assets/` and you wanted requests to each unique asset to be handled by a consistent version, you could define the following [request header transform rule](https://developers.cloudflare.com/rules/transform/request-header-modification/): Text in **Expression Editor**: ``` starts_with(http.request.uri.path, "/asset/") ``` Selected operation under **Modify request header**: _Set dynamic_ **Header name**: `Cloudflare-Workers-Version-Key` **Value**: `regex_replace(http.request.uri.path, "/asset/(.*)", "${1}")` ## Version overrides You can use version overrides to send a request to a specific version of your Worker in your gradual deployment. To specify a version override in your request, you can set the `Cloudflare-Workers-Version-Overrides` header on the request to your Worker. For example: Terminal window ``` curl -s https://example.com -H 'Cloudflare-Workers-Version-Overrides: my-worker-name="dc8dcd28-271b-4367-9840-6c244f84cb40"' ``` `Cloudflare-Workers-Version-Overrides` is a [Dictionary Structured Header ↗](https://www.rfc-editor.org/rfc/rfc8941#name-dictionaries). The dictionary can contain multiple key-value pairs. Each key indicates the name of the Worker the override should be applied to. The value indicates the version ID that should be used and must be a [String ↗](https://www.rfc-editor.org/rfc/rfc8941#name-strings). A version override will only be applied if the specified version is in the current deployment. The versions in the current deployment can be found using the [wrangler deployments list](https://developers.cloudflare.com/workers/wrangler/commands/general/#deployments-list) command or on the **Workers & Pages** page of the Cloudflare dashboard > Select your Workers > Deployments > Active Deployment. Verifying that the version override was applied There are a number of reasons why a request's version override may not be applied. For example: * The deployment containing the specified version may not have propagated yet. * The header value may not be a valid [Dictionary ↗](https://www.rfc-editor.org/rfc/rfc8941#name-dictionaries). In the case that a request's version override is not applied, the request will be routed according to the percentages set in the gradual deployment configuration. To make sure that the request's version override was applied correctly, you can [observe](#observability) the version of your Worker that was invoked. You could even automate this check by using the [runtime binding](#runtime-binding) to return the version in the Worker's response. ### Example You may want to test a new version in production before gradually deploying it to an increasing proportion of external traffic. In this example, your deployment is initially configured to route all traffic to a single version: | Version ID | Percentage | | ------------------------------------ | ---------- | | db7cd8d3-4425-4fe7-8c81-01bf963b6067 | 100% | Create a new deployment using [wrangler versions deploy](https://developers.cloudflare.com/workers/wrangler/commands/general/#versions-deploy) and specify 0% for the new version whilst keeping the previous version at 100%. | Version ID | Percentage | | ------------------------------------ | ---------- | | dc8dcd28-271b-4367-9840-6c244f84cb40 | 0% | | db7cd8d3-4425-4fe7-8c81-01bf963b6067 | 100% | Now test the new version with a version override before gradually progressing the new version to 100%: Terminal window ``` curl -s https://example.com -H 'Cloudflare-Workers-Version-Overrides: my-worker-name="dc8dcd28-271b-4367-9840-6c244f84cb40"' ``` ## Gradual deployments for Durable Objects To provide [global uniqueness](https://developers.cloudflare.com/durable-objects/platform/known-issues/#global-uniqueness), only one version of each [Durable Object](https://developers.cloudflare.com/durable-objects/) can run at a time. This means that gradual deployments work slightly differently for Durable Objects. When you create a new gradual deployment for a Worker with Durable Objects, each Durable Object is assigned a Worker version based on the percentages you configured in your [deployment](https://developers.cloudflare.com/workers/configuration/versions-and-deployments/#deployments). This version will not change until you create a new deployment. ![Gradual Deployments Durable Objects](https://developers.cloudflare.com/_astro/durable-objects.D92CiuSQ_1zYrvV.webp) ### Example This example assumes that you have previously created 3 Durable Object instances with names "foo", "bar" and "baz". Your Worker is currently on a version that we will call version "A" and you want to gradually deploy a new version "B" of your Worker. Here is how the versions of your Durable Objects might change as you progress your gradual deployment: | Deployment config | "foo" | "bar" | "baz" | | ------------------------------ | ----- | ----- | ----- | | Version A: 100% | A | A | A | | Version B: 20% Version A: 80% | B | A | A | | Version B: 50% Version A: 50% | B | B | A | | Version B: 100% | B | B | B | This is only an example, so the versions assigned to your Durable Objects may be different. However, the following is guaranteed: * For a given deployment, requests to each Durable Object will always use the same Worker version. * When you specify each version in the same order as the previous deployment and increase the percentage of a version, Durable Objects which were previously assigned that version will not be assigned a different version. In this example, Durable Object "foo" would never revert from version "B" to version "A". * The Durable Object will only be [reset](https://developers.cloudflare.com/durable-objects/observability/troubleshooting/#durable-object-reset-because-its-code-was-updated) when it is assigned a different version, so each Durable Object will only be reset once in this example. Note Typically, a Worker bundle will define both the Durable Object class and a Worker that interacts with it. In this case, you cannot deploy changes to your Durable Object and its Worker independently. You should ensure that API changes between your Durable Object and its Worker are [forwards and backwards compatible](https://developers.cloudflare.com/durable-objects/platform/known-issues/#code-updates) whether you are using gradual deployments or not. However, using gradual deployments will make it even more likely that different versions of your Durable Objects and its Worker will interact with each other. ### Migrations Versions of Worker bundles containing new Durable Object migrations cannot be uploaded. This is because Durable Object migrations are atomic operations. Once a migration is deployed, rollbacks cannot take place to any version prior to the one that included the migration. Durable Object migrations can be deployed with the following command: npm yarn pnpm ``` npx wrangler deploy ``` ``` yarn wrangler deploy ``` ``` pnpm wrangler deploy ``` To limit the blast radius of Durable Object migration deployments, migrations should be deployed independently of other code changes. To understand why Durable Object migrations are atomic operations, consider the hypothetical example of gradually deploying a delete migration. If a delete migration were applied to 50% of Durable Object instances, then Workers requesting those Durable Object instances would fail because they would have been deleted. To do this without producing errors, a version of the Worker which does not depend on any Durable Object instances would have to have already been rolled out. Then, you can deploy a delete migration without affecting any traffic and there is no reason to do so gradually. ## Observability When using gradual deployments, you may want to attribute Workers invocations to a specific version in order to get visibility into the impact of deploying new versions. ### Logpush A new `ScriptVersion` object is available in [Workers Logpush](https://developers.cloudflare.com/workers/observability/logs/logpush/). `ScriptVersion` can only be added through the Logpush API right now. Sample API call: Terminal window ``` curl -X POST 'https://api.cloudflare.com/client/v4/accounts//logpush/jobs' \ -H 'Authorization: Bearer ' \ -H 'Content-Type: application/json' \ -d '{ "name": "workers-logpush", "output_options": { "field_names": ["Event", "EventTimestampMs", "Outcome", "Logs", "ScriptName", "ScriptVersion"], }, "destination_conf": "", "dataset": "workers_trace_events", "enabled": true }'| jq . ``` Explain Code `ScriptVersion` is an object with the following structure: ``` scriptVersion: { id: "", message: "", tag: "" } ``` ### Runtime binding Use the [Version metadata binding](https://developers.cloudflare.com/workers/runtime-apis/bindings/version-metadata/) in to access version ID or version tag in your Worker. ## Limits ### Deployments limit You can only create a new deployment with the last 100 uploaded versions of your Worker. ```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/configuration/","name":"Configuration"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/configuration/versions-and-deployments/","name":"Versions & Deployments"}},{"@type":"ListItem","position":5,"item":{"@id":"/workers/configuration/versions-and-deployments/gradual-deployments/","name":"Gradual deployments"}}]} ``` --- --- title: Rollbacks description: Revert to an older version of your Worker. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/configuration/versions-and-deployments/rollbacks.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Rollbacks You can roll back to a previously deployed [version](https://developers.cloudflare.com/workers/configuration/versions-and-deployments/#versions) of your Worker using [Wrangler](https://developers.cloudflare.com/workers/wrangler/commands/general/#rollback) or the Cloudflare dashboard. Rolling back to a previous version of your Worker will immediately create a new [deployment](https://developers.cloudflare.com/workers/configuration/versions-and-deployments/#deployments) with the version specified and become the active deployment across all your deployed routes and domains. You can roll back from any deployment, including: * A single-version deployment (rolling back replaces the current version with the selected version). * A [split deployment](https://developers.cloudflare.com/workers/configuration/versions-and-deployments/gradual-deployments/) with two versions (rolling back replaces both versions with the selected version at 100% traffic). ## Via Wrangler To roll back to a specified version of your Worker via Wrangler, use the [wrangler rollback](https://developers.cloudflare.com/workers/wrangler/commands/general/#rollback) command. ## Via the Cloudflare Dashboard To roll back to a specified version of your Worker via the Cloudflare dashboard: 1. In the Cloudflare dashboard, go to the **Workers & Pages** page. [ Go to **Workers & Pages** ](https://dash.cloudflare.com/?to=/:account/workers-and-pages) 2. Select your Worker > **Deployments**. 3. Select the three dot icon on the right of the version you would like to roll back to and select **Rollback**. ## Rolling back from a split deployment If you are using a [gradual deployment](https://developers.cloudflare.com/workers/configuration/versions-and-deployments/gradual-deployments/) with two versions splitting traffic, rolling back will: 1. Replace the split deployment with a single-version deployment. 2. Route 100% of traffic to the version you selected for rollback. This effectively promotes one version to handle all traffic, which is useful if you notice issues with one of the versions in your split deployment and want to revert to a stable version immediately. To roll back from a split deployment: 1. Identify which version in your split deployment is stable and performing correctly. 2. Use the [rollback procedure](#via-wrangler) or [dashboard rollback](#via-the-cloudflare-dashboard) to roll back to that version. 3. The split deployment will be replaced with the selected version at 100% traffic. Warning **[Resources connected to your Worker](https://developers.cloudflare.com/workers/runtime-apis/bindings/) will not be changed during a rollback.** Errors could occur if using code for a prior version if the structure of data has changed between the version in the active deployment and the version selected to rollback to. ## Limits ### Rollbacks limit You can only roll back to the 100 most recently published versions. Note When using Wrangler in interactive mode, you can select from up to 100 recent versions. To roll back to a specific version, you can also specify the version ID directly on the command line. Refer to the [wrangler rollback](https://developers.cloudflare.com/workers/wrangler/commands/general/#rollback) documentation for details on specifying version IDs. ### Bindings You cannot roll back to a previous version of your Worker if the [Cloudflare Developer Platform resources](https://developers.cloudflare.com/workers/runtime-apis/bindings/) (such as [KV](https://developers.cloudflare.com/kv/) and [D1](https://developers.cloudflare.com/d1/)) have been deleted or modified between the version selected to roll back to and the version in the active deployment. Specifically, rollbacks will not be allowed if: * A [Durable Object migration](https://developers.cloudflare.com/durable-objects/reference/durable-objects-migrations/) has occurred between the version in the active deployment and the version selected to roll back to. * If the target deployment has a [binding](https://developers.cloudflare.com/workers/runtime-apis/bindings/) to an R2 bucket, KV namespace, or queue that no longer exists. ```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/configuration/","name":"Configuration"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/configuration/versions-and-deployments/","name":"Versions & Deployments"}},{"@type":"ListItem","position":5,"item":{"@id":"/workers/configuration/versions-and-deployments/rollbacks/","name":"Rollbacks"}}]} ``` --- --- title: Page Rules description: Review the interaction between various Page Rules and Workers. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/configuration/workers-with-page-rules.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Page Rules Page Rules trigger certain actions whenever a request matches one of the URL patterns you define. You can define a page rule to trigger one or more actions whenever a certain URL pattern is matched. Refer to [Page Rules](https://developers.cloudflare.com/rules/page-rules/) to learn more about configuring Page Rules. ## Page Rules with Workers Cloudflare acts as a [reverse proxy ↗](https://www.cloudflare.com/learning/what-is-cloudflare/) to provide services, like Page Rules, to Internet properties. Your application's traffic will pass through a Cloudflare data center that is closest to the visitor. There are hundreds of these around the world, each of which are capable of running services like Workers and Page Rules. If your application is built on Workers and/or Pages, the [Cloudflare global network ↗](https://www.cloudflare.com/learning/serverless/glossary/what-is-edge-computing/) acts as your origin server and responds to requests directly from the Cloudflare global network. When using Page Rules with Workers, the following workflow is applied. 1. Request arrives at Cloudflare data center. 2. Cloudflare decides if this request is a Worker route. Because this is a Worker route, Cloudflare evaluates and disabled a number of features, including some that would be set by Page Rules. 3. Page Rules run as part of normal request processing with some features now disabled. 4. Worker executes. 5. Worker makes a same-zone or other-zone subrequest. Because this is a Worker route, Cloudflare disables a number of features, including some that would be set by Page Rules. Page Rules are evaluated both at the client-to-Worker request stage (step 2) and the Worker subrequest stage (step 5). If you are experiencing Page Rule errors when running Workers, contact your Cloudflare account team or [Cloudflare Support](https://developers.cloudflare.com/support/contacting-cloudflare-support/). ## Affected Page Rules The following Page Rules may not work as expected when an incoming request is matched to a Worker route: * Always Online * [Always Use HTTPS](https://developers.cloudflare.com/workers/configuration/workers-with-page-rules/#always-use-https) * [Automatic HTTPS Rewrites](https://developers.cloudflare.com/workers/configuration/workers-with-page-rules/#automatic-https-rewrites) * [Browser Cache TTL](https://developers.cloudflare.com/workers/configuration/workers-with-page-rules/#browser-cache-ttl) * [Browser Integrity Check](https://developers.cloudflare.com/workers/configuration/workers-with-page-rules/#browser-integrity-check) * [Cache Deception Armor](https://developers.cloudflare.com/workers/configuration/workers-with-page-rules/#cache-deception-armor) * [Cache Level](https://developers.cloudflare.com/workers/configuration/workers-with-page-rules/#cache-level) * Disable Apps * [Disable Zaraz](https://developers.cloudflare.com/workers/configuration/workers-with-page-rules/#disable-zaraz) * [Edge Cache TTL](https://developers.cloudflare.com/workers/configuration/workers-with-page-rules/#edge-cache-ttl) * [Email Obfuscation](https://developers.cloudflare.com/workers/configuration/workers-with-page-rules/#email-obfuscation) * [Forwarding URL](https://developers.cloudflare.com/workers/configuration/workers-with-page-rules/#forwarding-url) * Host Header Override * [IP Geolocation Header](https://developers.cloudflare.com/workers/configuration/workers-with-page-rules/#ip-geolocation-header) * [Origin Cache Control](https://developers.cloudflare.com/workers/configuration/workers-with-page-rules/#origin-cache-control) * [Rocket Loader](https://developers.cloudflare.com/workers/configuration/workers-with-page-rules/#rocket-loader) * [Security Level](https://developers.cloudflare.com/workers/configuration/workers-with-page-rules/#security-level) * [SSL](https://developers.cloudflare.com/workers/configuration/workers-with-page-rules/#ssl) This is because the default setting of these Page Rules will be disabled when Cloudflare recognizes that the request is headed to a Worker. Testing Due to ongoing changes to the Workers runtime, detailed documentation on how these rules will be affected are updated following testing. To learn what these Page Rules do, refer to [Page Rules](https://developers.cloudflare.com/rules/page-rules/). Same zone versus other zone A same zone subrequest is a request the Worker makes to an orange-clouded hostname in the same zone the Worker runs on. Depending on your DNS configuration, any request that falls outside that definition may be considered an other zone request by the Cloudflare network. ### Always Use HTTPS | Source | Target | Behavior | | ------ | ---------- | -------------- | | Client | Worker | Rule Respected | | Worker | Same Zone | Rule Ignored | | Worker | Other Zone | Rule Ignored | ### Automatic HTTPS Rewrites | Source | Target | Behavior | | ------ | ---------- | -------------- | | Client | Worker | Rule Ignored | | Worker | Same Zone | Rule Respected | | Worker | Other Zone | Rule Ignored | ### Browser Cache TTL | Source | Target | Behavior | | ------ | ---------- | -------------- | | Client | Worker | Rule Ignored | | Worker | Same Zone | Rule Respected | | Worker | Other Zone | Rule Ignored | ### Browser Integrity Check | Source | Target | Behavior | | ------ | ---------- | -------------- | | Client | Worker | Rule Respected | | Worker | Same Zone | Rule Ignored | | Worker | Other Zone | Rule Ignored | ### Cache Deception Armor | Source | Target | Behavior | | ------ | ---------- | -------------- | | Client | Worker | Rule Respected | | Worker | Same Zone | Rule Respected | | Worker | Other Zone | Rule Ignored | ### Cache Level | Source | Target | Behavior | | ------ | ---------- | -------------- | | Client | Worker | Rule Respected | | Worker | Same Zone | Rule Respected | | Worker | Other Zone | Rule Ignored | ### Disable Zaraz | Source | Target | Behavior | | ------ | ---------- | -------------- | | Client | Worker | Rule Respected | | Worker | Same Zone | Rule Respected | | Worker | Other Zone | Rule Ignored | ### Edge Cache TTL | Source | Target | Behavior | | ------ | ---------- | -------------- | | Client | Worker | Rule Respected | | Worker | Same Zone | Rule Respected | | Worker | Other Zone | Rule Ignored | ### Email Obfuscation | Source | Target | Behavior | | ------ | ---------- | -------------- | | Client | Worker | Rule Ignored | | Worker | Same Zone | Rule Respected | | Worker | Other Zone | Rule Ignored | ### Forwarding URL | Source | Target | Behavior | | ------ | ---------- | -------------- | | Client | Worker | Rule Ignored | | Worker | Same Zone | Rule Respected | | Worker | Other Zone | Rule Ignored | ### IP Geolocation Header | Source | Target | Behavior | | ------ | ---------- | -------------- | | Client | Worker | Rule Respected | | Worker | Same Zone | Rule Respected | | Worker | Other Zone | Rule Ignored | ### Origin Cache Control | Source | Target | Behavior | | ------ | ---------- | -------------- | | Client | Worker | Rule Respected | | Worker | Same Zone | Rule Respected | | Worker | Other Zone | Rule Ignored | ### Rocket Loader | Source | Target | Behavior | | ------ | ---------- | ------------ | | Client | Worker | Rule Ignored | | Worker | Same Zone | Rule Ignored | | Worker | Other Zone | Rule Ignored | ### Security Level | Source | Target | Behavior | | ------ | ---------- | -------------- | | Client | Worker | Rule Respected | | Worker | Same Zone | Rule Ignored | | Worker | Other Zone | Rule Ignored | ### SSL | Source | Target | Behavior | | ------ | ---------- | -------------- | | Client | Worker | Rule Respected | | Worker | Same Zone | Rule Respected | | Worker | Other Zone | Rule Ignored | ```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/configuration/","name":"Configuration"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/configuration/workers-with-page-rules/","name":"Page Rules"}}]} ``` --- --- title: CI/CD description: Set up continuous integration and continuous deployment for your Workers. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/ci-cd/index.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # CI/CD You can set up continuous integration and continuous deployment (CI/CD) for your Workers by using either the integrated build system, [Workers Builds](#workers-builds), or using [external providers](#external-cicd) to optimize your development workflow. ## Why use CI/CD? Using a CI/CD pipeline to deploy your Workers is a best practice because it: * Automates the build and deployment process, removing the need for manual `wrangler deploy` commands. * Ensures consistent builds and deployments across your team by using the same source control management (SCM) system. * Reduces variability and errors by deploying in a uniform environment. * Simplifies managing access to production credentials. ## Which CI/CD should I use? Choose [Workers Builds](https://developers.cloudflare.com/workers/ci-cd/builds) if you want a fully integrated solution within Cloudflare's ecosystem that requires minimal setup and configuration for GitHub or GitLab users. We recommend using [external CI/CD providers](https://developers.cloudflare.com/workers/ci-cd/external-cicd) if: * You have a self-hosted instance of GitHub or GitLabs, which is currently not supported in Workers Builds' [Git integration](https://developers.cloudflare.com/workers/ci-cd/builds/git-integration/) * You are using a Git provider that is not GitHub or GitLab ## Workers Builds [Workers Builds](https://developers.cloudflare.com/workers/ci-cd/builds) is Cloudflare's native CI/CD system that allows you to integrate with GitHub or GitLab to automatically deploy changes with each new push to a selected branch (e.g. `main`). ![Workers Builds Workflow Diagram](https://developers.cloudflare.com/_astro/workers-builds-workflow.Bmy3qIVc_Z1wM0ch.webp) Ready to streamline your Workers deployments? Get started with [Workers Builds](https://developers.cloudflare.com/workers/ci-cd/builds/#get-started). ## External CI/CD You can also choose to set up your CI/CD pipeline with an external provider. * [GitHub Actions](https://developers.cloudflare.com/workers/ci-cd/external-cicd/github-actions/) * [GitLab CI/CD](https://developers.cloudflare.com/workers/ci-cd/external-cicd/gitlab-cicd/) ```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/ci-cd/","name":"CI/CD"}}]} ``` --- --- title: Builds description: Use Workers Builds to integrate with Git and automatically build and deploy your Worker when pushing a change image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/ci-cd/builds/index.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Builds The Cloudflare [Git integration](https://developers.cloudflare.com/workers/ci-cd/builds/git-integration/) lets you connect a new or existing Worker to a GitHub or GitLab repository, enabling automated builds and deployments for your Worker on push. ## Get started ### Connect a new Worker To create a new Worker and connect it to a GitHub or GitLab repository: 1. In the Cloudflare dashboard, go to the **Workers & Pages** page. [ Go to **Workers & Pages** ](https://dash.cloudflare.com/?to=/:account/workers-and-pages) 2. Select **Create application**. 3. Select **Get started** next to **Import a repository**. 4. Under **Import a repository**, select a **Git account**. 5. Select the repository you want to import from the list. You can also use the search bar to narrow the results. 6. Configure your project and select **Save and Deploy**. 7. Preview your Worker at its provided [workers.dev](https://developers.cloudflare.com/workers/configuration/routing/workers-dev/) subdomain. ### Connect an existing Worker To connect an existing Worker to a GitHub or GitLab repository: 1. In the Cloudflare dashboard, go to the **Workers & Pages** page. [ Go to **Workers & Pages** ](https://dash.cloudflare.com/?to=/:account/workers-and-pages) 2. Select the Worker you want to connect to a repository. 3. Select **Settings** and then **Builds**. 4. Select **Connect** and follow the prompts to connect the repository to your Worker and configure your [build settings](https://developers.cloudflare.com/workers/ci-cd/builds/configuration/). 5. Push a commit to your Git repository to trigger a build and deploy to your Worker. Warning When connecting a repository to a Workers project, the Worker name in the Cloudflare dashboard must match the `name` in the Wrangler configuration file in the specified root directory, or the build will fail. This ensures that the Worker deployed from the repository is consistent with the Worker registered in the Cloudflare dashboard. For details, see [Workers name requirement](https://developers.cloudflare.com/workers/ci-cd/builds/troubleshoot/#workers-name-requirement). ## Automatic project configuration When you connect a repository that does not have a Wrangler configuration file, [autoconfig](https://developers.cloudflare.com/workers/framework-guides/automatic-configuration/) runs to detect your framework and create a [pull request](https://developers.cloudflare.com/workers/ci-cd/builds/automatic-prs/) to configure your project for Cloudflare Workers. 1. Autoconfig detects your framework and generates the necessary configuration 2. A pull request is created in your repository with the necessary configuration changes 3. A preview deployment is generated so you can test before merging 4. Once you merge the PR, your project is ready for deployment For details about supported frameworks and what files are created, refer to [Deploy an existing project](https://developers.cloudflare.com/workers/framework-guides/automatic-configuration/). For details about the PRs created, refer to [Automatic pull requests](https://developers.cloudflare.com/workers/ci-cd/builds/automatic-prs/). ## View build and preview URL You can monitor a build's status and its build logs by navigating to **View build history** at the bottom of the **Deployments** tab of your Worker. If the build is successful, you can view the build details by selecting **View build** in the associated new [version](https://developers.cloudflare.com/workers/configuration/versions-and-deployments/) created under Version History. There you will also find the [preview URL](https://developers.cloudflare.com/workers/configuration/previews/) generated by the version under Version ID. Builds, versions, deployments If a build succeeds, it is uploaded as a version. If the build is configured to deploy (for example, with `wrangler deploy` set as the deploy command), the uploaded version will be automatically promoted to the Active Deployment. ## Disconnecting builds To disconnect a Worker from a GitHub or GitLab repository: 1. In the Cloudflare dashboard, go to the **Workers & Pages** page. [ Go to **Workers & Pages** ](https://dash.cloudflare.com/?to=/:account/workers-and-pages) 2. Select the Worker you want to disconnect from a repository. 3. Select **Settings** and then **Builds**. 4. Select **Disconnect**. If you want to switch to a different repository for your Worker, you must first disable builds, then reconnect to select the new repository. To disable automatic deployments while still allowing builds to run automatically and save as [versions](https://developers.cloudflare.com/workers/configuration/versions-and-deployments/) (without promoting them to an active deployment), update your deploy command to: `npx wrangler versions upload`. ```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/ci-cd/","name":"CI/CD"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/ci-cd/builds/","name":"Builds"}}]} ``` --- --- title: Advanced setups description: Learn how to use Workers Builds with more advanced setups image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/ci-cd/builds/advanced-setups.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Advanced setups ## Monorepos A monorepo is a single repository that contains multiple applications. This setup can be useful for a few reasons: * **Simplified dependency management**: Manage dependencies across all your workers and shared packages from a single place using tools like [pnpm workspaces ↗](https://pnpm.io/workspaces) and [syncpack ↗](https://syncpack.dev/). * **Code sharing and reuse**: Easily create and share common logic, types, and utilities between workers by creating shared packages. * **Atomic commits**: Changes affecting multiple workers or shared libraries can be committed together, making the history easier to understand and reducing the risk of inconsistencies. * **Consistent tooling**: Apply the same build, test, linting, and formatting configurations (e.g., via [Turborepo ↗](https://turborepo.com) in for task orchestration and shared configs in `packages/`) across all projects, ensuring consistent tooling and code quality across Workers. * **Easier refactoring**: Refactoring code that spans multiple Workers or shared packages is significantly easier within a single repository. #### Example Workers monorepos: * [cloudflare/mcp-server-cloudflare ↗](https://github.com/cloudflare/mcp-server-cloudflare) * [jahands/workers-monorepo-template ↗](https://github.com/jahands/workers-monorepo-template) * [cloudflare/templates ↗](https://github.com/cloudflare/templates) * [cloudflare/workers-sdk ↗](https://github.com/cloudflare/workers-sdk) ### Getting Started To set up a monorepo workflow: 1. Find the Workers associated with your project in the [Workers & Pages Dashboard ↗](https://dash.cloudflare.com). 2. Connect your monorepo to each Worker in the repository. 3. Set the root directory for each Worker to specify the location of its `wrangler.jsonc` and where build and deploy commands should run. 4. Optionally, configure unique build and deploy commands for each Worker. 5. Optionally, configure [build watch paths](https://developers.cloudflare.com/workers/ci-cd/builds/build-watch-paths/) for each Worker to monitor specific paths for changes. When a new commit is made to the monorepo, a new build and deploy will trigger for each Worker if the change is within each of its included watch paths. You can also check on the status of each build associated with your repository within GitHub with [check runs](https://developers.cloudflare.com/workers/ci-cd/builds/git-integration/github-integration/#check-run) or within GitLab with [commit statuses](https://developers.cloudflare.com/workers/ci-cd/builds/git-integration/gitlab-integration/#commit-status). ### Example In the example `ecommerce-monorepo`, a Workers project should be created for `product-service`, `order-service`, and `notification-service`. A Git connection to `ecommerce-monorepo` should be added in all of the Workers projects. If you are using a monorepo tool, such as [Turborepo ↗](https://turbo.build/), you can configure a different deploy command for each Worker, for example, `turbo deploy -F product-service`. Set the root directory of each Worker to where its Wrangler configuration file is located. For example, for `product-service`, the root directory should be `/workers/product-service/`. Optionally, you can add [build watch paths](https://developers.cloudflare.com/workers/ci-cd/builds/build-watch-paths/) to optimize your builds. When a new commit is made to `ecommerce-monorepo`, a build and deploy will be triggered for each of the Workers if the change is within its included watch paths using the configured commands for that Worker. * Directoryecommerce-monorepo/ * Directoryworkers/ * Directoryproduct-service/ * Directorysrc/ * … * wrangler.jsonc * Directoryorder-service/ * Directorysrc/ * … * wrangler.jsonc * Directorynotification-service/ * Directorysrc/ * … * wrangler.jsonc * Directorypackages/ * Directoryschema/ * … * README.md ## Wrangler Environments You can use [Wrangler Environments](https://developers.cloudflare.com/workers/wrangler/environments/) with Workers Builds by completing the following steps: 1. [Deploy via Wrangler](https://developers.cloudflare.com/workers/wrangler/commands/general/#deploy) to create the Workers for your environments on the Dashboard, if you do not already have them. 2. Find the Workers for your environments. They are typically named `[name of Worker] - [environment name]`. 3. Connect your repository to each of the Workers for your environment. 4. In each of the Workers, edit your Wrangler commands to include the flag `--env: ` in the build configurations for both the deploy command, and the non-production branch deploy command ([if applicable](https://developers.cloudflare.com/workers/ci-cd/builds/build-branches/#configure-non-production-branch-builds)). When a new commit is detected in the repository, a new build/deploy will trigger for each associated Worker. ### Example Imagine you have a Worker named `my-worker`, and you want to set up two environments `staging` and `production` set in the `wrangler.jsonc`. If you have not already, you can deploy `my-worker` for each environment using the commands `wrangler deploy --env staging` and `wrangler deploy --env production`. In your Cloudflare Dashboard, you should find the two Workers `my-worker-staging` and `my-worker-production`. Then, connect the Git repository for the Worker, `my-worker`, to both of the environment Workers. In the build configurations of each environment Worker, edit the deploy commands to be `npx wrangler deploy --env staging` and `npx wrangler deploy --env production` and the non-production branch deploy commands to be `npx wrangler versions upload --env staging` and `npx wrangler versions upload --env production` respectively. ```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/ci-cd/","name":"CI/CD"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/ci-cd/builds/","name":"Builds"}},{"@type":"ListItem","position":5,"item":{"@id":"/workers/ci-cd/builds/advanced-setups/","name":"Advanced setups"}}]} ``` --- --- title: Builds API reference description: Learn how to programmatically trigger builds, manage triggers, and monitor your Workers Builds using the API. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/ci-cd/builds/api-reference.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Builds API reference This guide shows you how to use the [Workers Builds REST API](https://developers.cloudflare.com/api/resources/workers%5Fbuilds/) to programmatically trigger builds, manage triggers, and monitor build status. The examples use `curl` commands that you can run directly in your terminal or adapt to your preferred programming language. Some examples pipe output through [jq ↗](https://jqlang.org/) to filter JSON responses — install it if you do not have it already. ## Before you start ### 1\. Create an API token with the correct permissions To use the Builds API, you need an API token to authenticate your requests. The Builds API requires a **user-scoped** API token, account-scoped tokens are not supported and will return "Invalid token" errors. Create your token at [dash.cloudflare.com/profile/api-tokens ↗](https://dash.cloudflare.com/profile/api-tokens) with the following permissions: | Permission | Access level | Why you need it | | ---------------------------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Workers Builds Configuration | Edit | Trigger builds, manage triggers, configure environment variables | | Workers Scripts | Read | Only needed for [one endpoint](#step-1-get-your-worker-tag) to retrieve your Worker's tag (documented as [external\_script\_id](#2-worker-tags-documented-as-external%5Fscript%5Fid)) | Note This API token is different from a **build token**. Build tokens are used by the build system to deploy your Worker. By default, Cloudflare automatically generates a build token for your account, but you can also [create your own](https://developers.cloudflare.com/workers/ci-cd/builds/configuration/#api-token-optional). The API token described above is what you use to call the Builds API itself. ### 2\. Worker tags (documented as external\_script\_id) The Builds API identifies Workers by their **tag**, an immutable UUID assigned by Cloudflare. In API responses and parameters, this value appears as `external_script_id`. | Identifier | Example | Where it comes from | | --------------------------------- | -------------------------------- | ------------------------------------- | | Worker name (id) | my-worker | The name you gave your Worker | | Worker tag (external\_script\_id) | 1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d | Immutable UUID assigned by Cloudflare | Every Builds API endpoint that references a Worker requires the **tag**, not the name. ### 3\. What is a trigger? A **trigger** is a configuration that defines how your Worker gets built and deployed. It specifies the build command, deploy command, environment variables, and which branches should trigger builds. Each Worker has up to **two triggers**: one for production (runs on your [production branch](https://developers.cloudflare.com/workers/ci-cd/builds/build-branches/#change-production-branch)) and one for preview (runs on all other branches). To set up triggers, refer to [Set up Workers Builds from scratch](#set-up-workers-builds-from-scratch). **Trigger fields:** | Field | Type | Description | | ----------------------- | ------- | ------------------------------------------------------------------------- | | trigger\_name | string | Display name for the trigger | | build\_command | string | Command to build your project (for example, npm run build) | | deploy\_command | string | Command to deploy your Worker (for example, npx wrangler deploy) | | root\_directory | string | Path to your project root | | branch\_includes | array | Branch patterns that trigger builds (for example, \["main"\] or \["\*"\]) | | branch\_excludes | array | Branch patterns to exclude | | path\_includes | array | File path patterns that trigger builds | | path\_excludes | array | File path patterns to ignore | | build\_caching\_enabled | boolean | Enable or disable build caching | | environment\_variables | object | Build-time variables specific to this trigger | ## Workflow overview Most Builds API operations follow this pattern: first get your Worker's tag, then get the trigger UUID, then perform build operations. ![Workflow overview: get Worker tag, then get trigger UUID, then perform build operations.](https://developers.cloudflare.com/_astro/workflow-overview.D-gY5w1T_2n0lJ2.svg) | Step | Action | Endpoint | | ---- | ---------------- | ------------------------------------------- | | 1 | Get Worker tag | GET /workers/scripts | | 2 | Get trigger UUID | GET /builds/workers/:worker\_tag/triggers | | 3a | Trigger a build | POST /builds/triggers/:trigger\_uuid/builds | | 3b | List builds | GET /builds/workers/:worker\_tag/builds | | 3c | Get build logs | GET /builds/builds/:build\_uuid/logs | | 3d | Cancel a build | PUT /builds/builds/:build\_uuid/cancel | ## Step 1: Get your Worker tag Call the [Workers Scripts API](https://developers.cloudflare.com/api/resources/workers/subresources/scripts/methods/list/) to list all your Workers and find the `tag` for the Worker you want to work with: Terminal window ``` curl -s "https://api.cloudflare.com/client/v4/accounts/{account_id}/workers/scripts" \ --header "Authorization: Bearer " \ | jq '.result[] | {name: .id, tag: .tag}' ``` Example output: ``` { "name": "my-worker", "tag": "1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d" } { "name": "another-worker", "tag": "8a1b2c3d4e5f67890abcdef123456789" } ``` Save the `tag` value for your Worker. You will use it in all subsequent API calls. ## Step 2: Get your trigger UUID Use the [GET /builds/workers/{tag}/triggers](https://developers.cloudflare.com/api/resources/workers%5Fbuilds/subresources/triggers/methods/list/) endpoint to list triggers for your Worker: Terminal window ``` curl -s "https://api.cloudflare.com/client/v4/accounts/{account_id}/builds/workers/{worker_tag}/triggers" \ --header "Authorization: Bearer " \ | jq '.result[] | {trigger_uuid, trigger_name, branch_includes, branch_excludes}' ``` Example output: ``` { "trigger_uuid": "f47ac10b-58cc-4372-a567-0e02b2c3d479", "trigger_name": "Deploy production", "branch_includes": ["main"], "branch_excludes": [] } { "trigger_uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "trigger_name": "Deploy non-production branches", "branch_includes": ["*"], "branch_excludes": ["main"] } ``` Explain Code Save the `trigger_uuid` for the trigger you want to work with. Remember, you will have at most two triggers: one for your production branch (for example, `main`) that deploys to your live Worker, and optionally one for all other branches that creates preview deployments. ## Step 3: Work with builds Now that you have the Worker tag and trigger UUID, you can trigger builds, list build history, and get logs. ### Trigger a manual build Use the [POST /builds/triggers/{uuid}/builds](https://developers.cloudflare.com/api/resources/workers%5Fbuilds/subresources/builds/methods/create/) endpoint with the `trigger_uuid` from [Step 2](#step-2-get-your-trigger-uuid). Terminal window ``` curl -s "https://api.cloudflare.com/client/v4/accounts/{account_id}/builds/triggers/{trigger_uuid}/builds" \ --header "Authorization: Bearer " \ --header "Content-Type: application/json" \ --request POST \ --data '{"branch": "main"}' ``` You must specify `branch`, `commit_hash`, or both: | Field | Description | | ------------ | -------------------------------------------------------------------------------------------------- | | branch | Git branch name to build (for example, main) | | commit\_hash | Specific commit SHA to build. If provided without branch, builds the commit on its current branch. | The response includes the `build_uuid` which you can use to monitor the build. ### List builds for a Worker Use the [GET /builds/workers/{tag}/builds](https://developers.cloudflare.com/api/resources/workers%5Fbuilds/subresources/builds/methods/list/) endpoint with the `worker_tag` from [Step 1](#step-1-get-your-worker-tag). Terminal window ``` curl -s "https://api.cloudflare.com/client/v4/accounts/{account_id}/builds/workers/{worker_tag}/builds" \ --header "Authorization: Bearer " \ | jq '.result[] | {build_uuid, status, branch, created_at}' ``` The response includes `build_uuid` for each build, which you need for getting logs or canceling builds. ### Get build logs Use the [GET /builds/builds/{uuid}/logs](https://developers.cloudflare.com/api/resources/workers%5Fbuilds/subresources/builds/methods/get%5Flogs/) endpoint. Get the `build_uuid` from: * [List builds](#list-builds-for-a-worker) * The response when [triggering a build](#trigger-a-manual-build) * [Get latest builds by script IDs](https://developers.cloudflare.com/api/resources/workers%5Fbuilds/subresources/builds/methods/get%5Flatest%5Fby%5Fscript%5Fids/) * The last segment of the URL on your build details page in the dashboard Terminal window ``` curl -s "https://api.cloudflare.com/client/v4/accounts/{account_id}/builds/builds/{build_uuid}/logs" \ --header "Authorization: Bearer " ``` ### Cancel a running build Use the [PUT /builds/builds/{uuid}/cancel](https://developers.cloudflare.com/api/resources/workers%5Fbuilds/subresources/builds/methods/cancel/) endpoint. Get the `build_uuid` from: * [List builds](#list-builds-for-a-worker) * The response when [triggering a build](#trigger-a-manual-build) * [Get latest builds by script IDs](https://developers.cloudflare.com/api/resources/workers%5Fbuilds/subresources/builds/methods/get%5Flatest%5Fby%5Fscript%5Fids/) * The last segment of the URL on your build details page in the dashboard Terminal window ``` curl -s "https://api.cloudflare.com/client/v4/accounts/{account_id}/builds/builds/{build_uuid}/cancel" \ --header "Authorization: Bearer " \ --request PUT ``` ## Update trigger configuration Use the [PATCH /builds/triggers/{uuid}](https://developers.cloudflare.com/api/resources/workers%5Fbuilds/subresources/triggers/methods/update/) endpoint with the `trigger_uuid` from [Step 2](#step-2-get-your-trigger-uuid). You can update any of the trigger fields described in [What is a trigger?](#3-what-is-a-trigger). Terminal window ``` curl -s "https://api.cloudflare.com/client/v4/accounts/{account_id}/builds/triggers/{trigger_uuid}" \ --header "Authorization: Bearer " \ --header "Content-Type: application/json" \ --request PATCH \ --data '{ "build_command": "npm run build:prod", "deploy_command": "npx wrangler deploy" }' ``` ## Manage build environment variables Environment variables are set per trigger, meaning you can have different values for production and preview builds. For example, you might set `NODE_ENV=production` on your production trigger and `NODE_ENV=development` on your preview trigger. Refer to the [environment variables API reference](https://developers.cloudflare.com/api/resources/workers%5Fbuilds/subresources/environment%5Fvariables/) for full endpoint details. Note These are **build-time** environment variables, available only during the build process. For runtime environment variables, refer to [Environment variables](https://developers.cloudflare.com/workers/configuration/environment-variables/). ### List environment variables Use the `trigger_uuid` from [Step 2](#step-2-get-your-trigger-uuid). Terminal window ``` curl -s "https://api.cloudflare.com/client/v4/accounts/{account_id}/builds/triggers/{trigger_uuid}/environment_variables" \ --header "Authorization: Bearer " ``` ### Set environment variables You can set different variables for each trigger. For example, to set production environment variables: Terminal window ``` curl -s "https://api.cloudflare.com/client/v4/accounts/{account_id}/builds/triggers/{production_trigger_uuid}/environment_variables" \ --header "Authorization: Bearer " \ --header "Content-Type: application/json" \ --request PATCH \ --data '{ "variables": [ {"key": "NODE_ENV", "value": "production", "type": "text"}, {"key": "API_KEY", "value": "prod-secret-key", "type": "secret"} ] }' ``` Explain Code And different values for preview builds: Terminal window ``` curl -s "https://api.cloudflare.com/client/v4/accounts/{account_id}/builds/triggers/{preview_trigger_uuid}/environment_variables" \ --header "Authorization: Bearer " \ --header "Content-Type: application/json" \ --request PATCH \ --data '{ "variables": [ {"key": "NODE_ENV", "value": "development", "type": "text"}, {"key": "API_KEY", "value": "dev-secret-key", "type": "secret"} ] }' ``` Explain Code Use `type: "text"` for plain values and `type: "secret"` for sensitive values that should be masked in logs. ### Delete an environment variable Use the `trigger_uuid` from [Step 2](#step-2-get-your-trigger-uuid). The `variable_key` is the key name you set (for example, `NODE_ENV`). Terminal window ``` curl -s "https://api.cloudflare.com/client/v4/accounts/{account_id}/builds/triggers/{trigger_uuid}/environment_variables/{variable_key}" \ --header "Authorization: Bearer " \ --request DELETE ``` ## Purge build cache Use the [POST /builds/triggers/{uuid}/purge\_build\_cache](https://developers.cloudflare.com/api/resources/workers%5Fbuilds/subresources/triggers/methods/purge%5Fbuild%5Fcache/) endpoint with the `trigger_uuid` from [Step 2](#step-2-get-your-trigger-uuid). This clears cached dependencies and build artifacts for that trigger. Terminal window ``` curl -s "https://api.cloudflare.com/client/v4/accounts/{account_id}/builds/triggers/{trigger_uuid}/purge_build_cache" \ --header "Authorization: Bearer " \ --request POST ``` ## Examples The following examples show common use cases for the Builds API. ### Set up Workers Builds from scratch This example walks through the complete process of connecting a GitHub repository to a Worker and setting up automated builds using only the API. ![Setup flow: get GitHub IDs, create repo connection, get Worker tag, create triggers, set env variables, trigger first build.](https://developers.cloudflare.com/_astro/setup-from-scratch.BUpowztp_1X7alF.svg) | Step | Action | Endpoint | | ---- | --------------------------- | ------------------------------------------------------------- | | 1 | Get GitHub account/repo IDs | GET api.github.com/users/... and GET api.github.com/repos/... | | 2 | Create repo connection | PUT /builds/repos/connections | | 3 | Get Worker tag | GET /workers/scripts | | 4a | Create production trigger | POST /builds/triggers | | 4b | Create preview trigger | POST /builds/triggers | | 5 | Set environment variables | PATCH /builds/triggers/:trigger\_uuid/environment\_variables | | 6 | Trigger first build | POST /builds/triggers/:trigger\_uuid/builds | #### Prerequisites Before using the API, you must first install the Cloudflare GitHub App through the dashboard: 1. Go to **Workers & Pages** in the [Cloudflare dashboard ↗](https://dash.cloudflare.com). 2. Select any Worker and go to **Settings** \> **Builds** \> **Connect**. 3. Select **GitHub** and authorize the Cloudflare GitHub App for your account or organization. This one-time setup creates the connection between your GitHub account and Cloudflare. Once complete, you can use the API for everything else. #### Step 1: Get your GitHub account information After installing the GitHub App, you need your GitHub account ID and repository ID. You can find these from an existing trigger or from the GitHub API. From GitHub's API: Terminal window ``` # Get your GitHub user/org ID curl -s "https://api.github.com/users/" | jq '.id' # Get a repository ID curl -s "https://api.github.com/repos//" | jq '.id' ``` #### Step 2: Create a repository connection Create a connection between your GitHub repository and Cloudflare: Terminal window ``` curl -s "https://api.cloudflare.com/client/v4/accounts/{account_id}/builds/repos/connections" \ --header "Authorization: Bearer " \ --header "Content-Type: application/json" \ --request PUT \ --data '{ "provider_type": "github", "provider_account_id": "", "provider_account_name": "", "repo_id": "", "repo_name": "" }' ``` Explain Code Save the `repo_connection_uuid` from the response. #### Step 3: Get your Worker tag Terminal window ``` curl -s "https://api.cloudflare.com/client/v4/accounts/{account_id}/workers/scripts" \ --header "Authorization: Bearer " \ | jq '.result[] | {name: .id, tag: .tag}' ``` #### Step 4: Create a production trigger Create a trigger that deploys when you push to `main`: Terminal window ``` curl -s "https://api.cloudflare.com/client/v4/accounts/{account_id}/builds/triggers" \ --header "Authorization: Bearer " \ --header "Content-Type: application/json" \ --request POST \ --data '{ "external_script_id": "", "repo_connection_uuid": "", "trigger_name": "Deploy production", "build_command": "npm run build", "deploy_command": "npx wrangler deploy", "root_directory": "/", "branch_includes": ["main"], "branch_excludes": [], "path_includes": ["*"], "path_excludes": [] }' ``` Explain Code #### Step 5: Create a preview trigger (optional) Create a second trigger for preview deployments on all other branches: Terminal window ``` curl -s "https://api.cloudflare.com/client/v4/accounts/{account_id}/builds/triggers" \ --header "Authorization: Bearer " \ --header "Content-Type: application/json" \ --request POST \ --data '{ "external_script_id": "", "repo_connection_uuid": "", "trigger_name": "Deploy preview branches", "build_command": "npm run build", "deploy_command": "npx wrangler versions upload", "root_directory": "/", "branch_includes": ["*"], "branch_excludes": ["main"], "path_includes": ["*"], "path_excludes": [] }' ``` Explain Code Note the different `deploy_command`: production uses `wrangler deploy` while preview uses `wrangler versions upload` to create preview URLs without affecting the live deployment. #### Step 6: Set environment variables for each trigger Set production environment variables: Terminal window ``` curl -s "https://api.cloudflare.com/client/v4/accounts/{account_id}/builds/triggers/{production_trigger_uuid}/environment_variables" \ --header "Authorization: Bearer " \ --header "Content-Type: application/json" \ --request PATCH \ --data '{ "variables": [ {"key": "NODE_ENV", "value": "production", "type": "text"} ] }' ``` Set preview environment variables: Terminal window ``` curl -s "https://api.cloudflare.com/client/v4/accounts/{account_id}/builds/triggers/{preview_trigger_uuid}/environment_variables" \ --header "Authorization: Bearer " \ --header "Content-Type: application/json" \ --request PATCH \ --data '{ "variables": [ {"key": "NODE_ENV", "value": "development", "type": "text"} ] }' ``` #### Step 7: Trigger your first build Terminal window ``` curl -s "https://api.cloudflare.com/client/v4/accounts/{account_id}/builds/triggers/{production_trigger_uuid}/builds" \ --header "Authorization: Bearer " \ --header "Content-Type: application/json" \ --request POST \ --data '{"branch": "main"}' ``` Your Worker is now connected to GitHub. Future pushes to `main` will automatically trigger production deployments, and pushes to other branches will create preview deployments. ### Redeploy current deployment Redeploy your current active deployment to refresh build-time data. This is useful when you need to rebuild without code changes. ![Redeploy flow: get active deployment, find the build for that version, retrigger with same branch and commit.](https://developers.cloudflare.com/_astro/redeploy-flow.WidssEDb_Z1MmgGB.svg) | Step | Action | Endpoint | | ---- | --------------------------------- | ---------------------------------------------- | | 1 | Get active deployment | GET /workers/scripts/:worker\_name/deployments | | 2 | Find the build for that version | GET /builds/builds?version\_ids=:version\_id | | 3 | Retrigger with same branch/commit | POST /builds/triggers/:trigger\_uuid/builds | **Step 1: Get the active deployment's version ID** Use the [GET /workers/scripts/{script\_name}/deployments](https://developers.cloudflare.com/api/resources/workers/subresources/scripts/subresources/deployments/methods/list/) endpoint with the `worker_name` from [Step 1](#step-1-get-your-worker-tag): Terminal window ``` curl -s "https://api.cloudflare.com/client/v4/accounts/{account_id}/workers/scripts/{worker_name}/deployments" \ --header "Authorization: Bearer " \ | jq '.result.deployments[0].versions[0].version_id' ``` Save the `version_id` from the output. **Step 2: Find the build for that version** Use the [GET /builds/builds](https://developers.cloudflare.com/api/resources/workers%5Fbuilds/subresources/builds/methods/get%5Fby%5Fversion%5Fids/) endpoint with the `version_id` from the previous step: Terminal window ``` curl -s "https://api.cloudflare.com/client/v4/accounts/{account_id}/builds/builds?version_ids={version_id}" \ --header "Authorization: Bearer " \ | jq '.result.builds' ``` From the response, note the `trigger.trigger_uuid`, `build_trigger_metadata.branch`, and `build_trigger_metadata.commit_hash`. **Step 3: Retrigger with the same branch and commit** Use the [POST /builds/triggers/{uuid}/builds](https://developers.cloudflare.com/api/resources/workers%5Fbuilds/subresources/builds/methods/create/) endpoint with the values from the previous step: Terminal window ``` curl -s "https://api.cloudflare.com/client/v4/accounts/{account_id}/builds/triggers/{trigger_uuid}/builds" \ --header "Authorization: Bearer " \ --header "Content-Type: application/json" \ --request POST \ --data '{ "branch": "{branch}", "commit_hash": "{commit_hash}" }' Passing both `branch` and `commit_hash` pins the build to that exact commit on that branch. ``` Explain Code ## Troubleshooting ### "Resource not found" error You are likely using the Worker name instead of the Worker tag. The Builds API requires the `tag` (a UUID like `1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d`), not the Worker name. Refer to [Step 1](#step-1-get-your-worker-tag) to get your Worker tag. For other build errors, refer to [Troubleshooting builds](https://developers.cloudflare.com/workers/ci-cd/builds/troubleshoot/). ## Related resources * [Workers Builds REST API reference](https://developers.cloudflare.com/api/resources/workers%5Fbuilds/) \- Complete endpoint documentation * [Workers Scripts REST API reference](https://developers.cloudflare.com/api/resources/workers/subresources/scripts/) \- For retrieving Worker tags * [Workers Builds overview](https://developers.cloudflare.com/workers/ci-cd/builds/) \- Dashboard setup and configuration * [Build configuration](https://developers.cloudflare.com/workers/ci-cd/builds/configuration/) \- Build settings and options * [Create API token](https://developers.cloudflare.com/fundamentals/api/get-started/create-token/) \- How to create tokens with the correct permissions ```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/ci-cd/","name":"CI/CD"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/ci-cd/builds/","name":"Builds"}},{"@type":"ListItem","position":5,"item":{"@id":"/workers/ci-cd/builds/api-reference/","name":"Builds API reference"}}]} ``` --- --- title: Automatic pull requests description: Learn about the pull requests Workers Builds creates to configure your project or resolve issues. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/ci-cd/builds/automatic-prs.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Automatic pull requests Workers Builds can automatically create pull requests in your repository to configure your project or resolve deployment issues. ## Configuration PR When you connect a repository that does not have a Wrangler configuration file, Workers Builds runs `wrangler deploy` which triggers [automatic project configuration](https://developers.cloudflare.com/workers/framework-guides/automatic-configuration/). Instead of failing, it creates a pull request with the necessary configuration for your detected framework. Note A configuration PR is only created when your deploy command is `npx wrangler deploy`. If you have a custom deploy command, autoconfig will still run and configure your project, but no PR will be created. ### Why you should merge the PR Without the configuration in your repository, every build has to run autoconfig first, which means your project gets built twice - once during autoconfig to generate the configuration, and again for the actual deployment. Merging the PR commits the configuration to your repository, so future builds skip autoconfig and go straight to building and deploying. This results in faster deployments and version-controlled settings. ### What the PR includes ![Example of an automatic configuration pull request created by Workers Builds](https://developers.cloudflare.com/_astro/automatic-pr.CwJG6Bec_1cC506.webp) The configuration PR may contain changes to the following files, depending on your framework: * **`wrangler.jsonc`** \- Wrangler configuration file with your Worker settings * **Framework adapter** \- Any required Cloudflare adapter for your framework (for example, `@astrojs/cloudflare` for Astro) * **Framework configuration** \- Updates to framework config files (for example, `astro.config.mjs` for Astro or `svelte.config.js` for SvelteKit) * **`package.json`** \- New scripts like `deploy`, `preview`, and `cf-typegen`, plus required dependencies * **`package-lock.json`** / **`yarn.lock`** / **`pnpm-lock.yaml`** \- Updated lock file with new dependencies * **`.gitignore`** \- Entries for `.wrangler` and `.dev.vars*` files * **`.assetsignore`** \- For frameworks that generate worker files in the output directory ### PR description The PR description includes: * **Detected settings** \- Framework, build command, deploy command, and version command * **Preview link** \- A working preview generated using the detected settings * **Next steps** \- Links to documentation for adding bindings, custom domains, and more Note When you merge the PR, Workers Builds will update your build and deploy commands if they do not match the detected settings, ensuring successful deployments. ## Name conflict PR If Workers Builds detects a mismatch between your Worker name in the Cloudflare dashboard and the `name` field in your Wrangler configuration file, it will create a pull request to fix the conflict. This can happen when: * You rename your Worker in the dashboard but not in your config file * You connect a repository that was previously used with a different Worker * The `name` field in your config does not match the connected Worker The PR will update the `name` field in your Wrangler configuration to match the Worker name in the dashboard. For more details, refer to the [name conflict changelog](https://developers.cloudflare.com/changelog/2025-02-20-builds-name-conflict/). ## Reviewing PRs When you receive a PR from Workers Builds: 1. **Review the changes** \- Check that the configuration matches your project requirements 2. **Test the preview** \- Use the preview link in the PR description to verify everything works 3. **Merge when ready** \- Once satisfied, merge the PR to enable faster deployments ```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/ci-cd/","name":"CI/CD"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/ci-cd/builds/","name":"Builds"}},{"@type":"ListItem","position":5,"item":{"@id":"/workers/ci-cd/builds/automatic-prs/","name":"Automatic pull requests"}}]} ``` --- --- title: Build branches description: Configure which git branches should trigger a Workers Build image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/ci-cd/builds/build-branches.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Build branches When you connect a git repository to Workers, commits made on the production git branch will produce a Workers Build. If you want to take advantage of [preview URLs](https://developers.cloudflare.com/workers/configuration/previews/) and [pull request comments](https://developers.cloudflare.com/workers/ci-cd/builds/git-integration/github-integration/#pull-request-comment), you can additionally enable "non-production branch builds" in order to trigger a build on all branches of your repository. ## Change production branch To change the production branch of your project: 1. In **Overview**, select your Workers project. 2. Go to **Settings** \> **Build** \> **Branch control**. Workers will default to the default branch of your git repository, but this can be changed in the dropdown. Every push event made to this branch will trigger a build and execute the [build command](https://developers.cloudflare.com/workers/ci-cd/builds/configuration/#deploy-command), followed by the [deploy command](https://developers.cloudflare.com/workers/ci-cd/builds/configuration/#deploy-command). ## Configure non-production branch builds To enable or disable non-production branch builds: 1. In **Overview**, select your Workers project. 2. Go to **Settings** \> **Build** \> **Branch control**. The checkbox **Builds for non-production branches** allows you to enable or disable builds for non-production branches. When enabled, every push event made to a non-production branch will trigger a build and execute the [build command](https://developers.cloudflare.com/workers/ci-cd/builds/configuration/#deploy-command), followed by the [non-production branch deploy command](https://developers.cloudflare.com/workers/ci-cd/builds/configuration/#non-production-branch-deploy-command). ```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/ci-cd/","name":"CI/CD"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/ci-cd/builds/","name":"Builds"}},{"@type":"ListItem","position":5,"item":{"@id":"/workers/ci-cd/builds/build-branches/","name":"Build branches"}}]} ``` --- --- title: Build caching description: Improve build times by caching build outputs and dependencies image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/ci-cd/builds/build-caching.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Build caching Improve Workers build times by caching dependencies and build output between builds with a project-wide shared cache. The first build to occur after enabling build caching on your Workers project will save relevant artifacts to cache. Every subsequent build will restore from cache unless configured otherwise. ## About build cache When enabled, build caching will automatically detect which package manager and framework the project is using from its `package.json` and cache data accordingly for the build. The following shows which package managers and frameworks are supported for dependency and build output caching respectively. ### Package managers Workers build cache will cache the global cache directories of the following package managers: | Package Manager | Directories cached | | ------------------------------- | ------------------ | | [npm ↗](https://www.npmjs.com/) | .npm | | [yarn ↗](https://yarnpkg.com/) | .cache/yarn | | [pnpm ↗](https://pnpm.io/) | .pnpm-store | | [bun ↗](https://bun.sh/) | .bun/install/cache | ### Frameworks Some frameworks provide a cache directory that is typically populated by the framework with intermediate build outputs or dependencies during build time. Workers Builds will automatically detect the framework you are using and cache this directory for reuse in subsequent builds. The following frameworks support build output caching: | Framework | Directories cached | | ---------- | ---------------------------------------- | | Astro | node\_modules/.astro | | Docusaurus | node\_modules/.cache, .docusaurus, build | | Eleventy | .cache | | Gatsby | .cache, public | | Next.js | .next/cache | | Nuxt | node\_modules/.cache/nuxt | | SvelteKit | node\_modules/.cache/imagetools | Note [Static assets](https://developers.cloudflare.com/workers/static-assets/) and [frameworks](https://developers.cloudflare.com/workers/framework-guides/) are now supported in Cloudflare Workers. ### Limits The following limits are imposed for build caching: * **Retention**: Cache is purged 7 days after its last read date. Unread cache artifacts are purged 7 days after creation. * **Storage**: Every project is allocated 10 GB. If the project cache exceeds this limit, the project will automatically start deleting artifacts that were read least recently. ## Enable build cache To enable build caching: 1. Navigate to [Workers & Pages Overview ↗](https://dash.cloudflare.com) on the Dashboard. 2. Find your Workers project. 3. Go to **Settings** \> **Build** \> **Build cache**. 4. Select **Enable** to turn on build caching. ## Clear build cache The build cache can be cleared for a project when needed, such as when debugging build issues. To clear the build cache: 1. Navigate to [Workers & Pages Overview ↗](https://dash.cloudflare.com) on the Dashboard. 2. Find your Workers project. 3. Go to **Settings** \> **Build** \> **Build cache**. 4. Select **Clear Cache** to clear the build cache. ```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/ci-cd/","name":"CI/CD"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/ci-cd/builds/","name":"Builds"}},{"@type":"ListItem","position":5,"item":{"@id":"/workers/ci-cd/builds/build-caching/","name":"Build caching"}}]} ``` --- --- title: Build image description: Understand the build image used in Workers Builds. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/ci-cd/builds/build-image.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Build image Workers Builds uses a build image with support for a variety of languages and tools such as Node.js, Python, PHP, Ruby, and Go. ## Supported Tooling Workers Builds supports a variety of runtimes, languages, and tools. Builds will use the default versions listed below unless a custom version is detected or specified. You can [override the default versions](https://developers.cloudflare.com/workers/ci-cd/builds/build-image/#overriding-default-versions) using environment variables or version files. All versions are available for override. Default version updates The default versions will be updated regularly to the latest minor version. No major version updates will be made without notice. If you need a specific minor version, please specify it by [overriding the default version](https://developers.cloudflare.com/workers/ci-cd/builds/build-image/#overriding-default-versions). ### Runtime | Tool | Default version | Environment variable | File | | ----------- | --------------- | -------------------- | ---------------------------- | | **Go** | 1.24.3 | GO\_VERSION | | | **Node.js** | 22.16.0 | NODE\_VERSION | .nvmrc, .node-version | | **Python** | 3.13.3 | PYTHON\_VERSION | .python-version, runtime.txt | | **Ruby** | 3.4.4 | RUBY\_VERSION | .ruby-version | ### Tools and languages | Tool | Default version | Environment variable | | ----------- | ----------------- | -------------------- | | **Bun** | 1.2.15 | BUN\_VERSION | | **Hugo** | extended\_0.147.7 | HUGO\_VERSION | | **npm** | 10.9.2 | | | **yarn** | 4.9.1 | YARN\_VERSION | | **pnpm** | 10.11.1 | PNPM\_VERSION | | **pip** | 25.1.1 | | | **gem** | 3.6.9 | | | **poetry** | 2.1.3 | | | **pipx** | 1.7.1 | | | **bundler** | 2.6.9 | | ## Advanced Settings ### Overriding Default Versions If you need to override a [specific version](https://developers.cloudflare.com/workers/ci-cd/builds/build-image/#overriding-default-versions) of a language or tool within the image, you can specify it as a [build environment variable](https://developers.cloudflare.com/workers/ci-cd/builds/configuration/#build-settings), or set the relevant file in your source code as shown above. To set the version using a build environment variables, you can: 1. Find the environment variable name for the language or tool and desired version (e.g. `NODE_VERSION = 22`) 2. Add and save the environment variable on the dashboard by going to **Settings** \> **Build** \> **Build Variables and Secrets** in your Workers project Or, to set the version by adding a file to your project, you can: 1. Find the filename for the language or tool (e.g. `.nvmrc`) 2. Add the specified file name to the root directory and set the desired version number as the file's content. For example, if the version number is 22, the file should contain '22'. ### Skip dependency install You can add the following build variable to disable automatic dependency installation and run a custom install command instead. | Build variable | Value | | ------------------------- | --------- | | SKIP\_DEPENDENCY\_INSTALL | 1 or true | ## Pre-installed Packages In the following table, review the pre-installed packages in the build image. The packages are installed with `apt`, a package manager for Linux distributions. | curl | libbz2-dev | libreadline-dev | | --------------- | --------------- | --------------- | | git | libc++1 | libssl-dev | | git-lfs | libdb-dev | libvips-dev | | unzip | libgdbm-dev | libyaml-dev | | autoconf | libgdbm6 | tzdata | | build-essential | libgbm1 | wget | | bzip2 | libgmp-dev | zlib1g-dev | | gnupg | liblzma-dev | zstd | | libffi-dev | libncurses5-dev | | ## Build Environment Workers Builds are run in the following environment: | **Build Environment** | Ubuntu 24.04 | | --------------------- | ------------ | | **Architecture** | x86\_64 | ## Build Image Policy ### Preinstalled Software Updates Preinstalled software (languages and tools) will be updated before reaching end-of-life (EOL). These updates apply only if you have not [overridden the default version](https://developers.cloudflare.com/workers/ci-cd/builds/build-image/#overriding-default-versions). * **Minor version updates**: May be updated to the latest available minor version without notice. For tools that do not follow semantic versioning (e.g., Bun or Hugo), updates that may contain breaking changes will receive 3 months’ notice. * **Major version updates**: Updated to the next stable long-term support (LTS) version with 3 months’ notice. **How you'll be notified (for changes requiring notice):** * [Cloudflare Changelog ↗](https://developers.cloudflare.com/changelog/) * Dashboard notifications for projects that will receive the update * Email notifications to project owners To maintain a specific version and avoid automatic updates, [override the default version](https://developers.cloudflare.com/workers/ci-cd/builds/build-image/#overriding-default-versions). ### Best Practices To avoid unexpected build failures: * **Monitor announcements** via the [Cloudflare Changelog ↗](https://developers.cloudflare.com/changelog/), dashboard notifications, and email * **Pin specific versions** of critical preinstalled software by [overriding default versions](https://developers.cloudflare.com/workers/ci-cd/builds/build-image/#overriding-default-versions) ```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/ci-cd/","name":"CI/CD"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/ci-cd/builds/","name":"Builds"}},{"@type":"ListItem","position":5,"item":{"@id":"/workers/ci-cd/builds/build-image/","name":"Build image"}}]} ``` --- --- title: Build watch paths description: Reduce compute for your monorepo by specifying paths for Workers Builds to skip image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/ci-cd/builds/build-watch-paths.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Build watch paths When you connect a git repository to Workers, by default a change to any file in the repository will trigger a build. You can configure Workers to include or exclude specific paths to specify if Workers should skip a build for a given path. This can be especially helpful if you are using a monorepo project structure and want to limit the amount of builds being kicked off. ## Configure Paths To configure which paths are included and excluded: 1. In **Overview**, select your Workers project. 2. Go to **Settings** \> **Build** \> **Build watch paths**. Workers will default to setting your project’s includes paths to everything (\[\*\]) and excludes paths to nothing (`[]`). The configuration fields can be filled in two ways: * **Static filepaths**: Enter the precise name of the file you are looking to include or exclude (for example, `docs/README.md`). * **Wildcard syntax:** Use wildcards to match multiple path directories. You can specify wildcards at the start or end of your rule. Wildcard syntax A wildcard (`*`) is a character that is used within rules. It can be placed alone to match anything or placed at the start or end of a rule to allow for better control over branch configuration. A wildcard will match zero or more characters.For example, if you wanted to match all branches that started with `fix/` then you would create the rule `fix/*` to match strings like `fix/1`, `fix/bugs`or `fix/`. For each path in a push event, build watch paths will be evaluated as follows: * Paths satisfying excludes conditions are ignored first * Any remaining paths are checked against includes conditions * If any matching path is found, a build is triggered. Otherwise the build is skipped Workers will bypass the path matching for a push event and default to building the project if: * A push event contains 0 file changes, in case a user pushes a empty push event to trigger a build * A push event contains 3000+ file changes or 20+ commits ## Examples ### Example 1 If you want to trigger a build from all changes within a set of directories, such as all changes in the folders `project-a/` and `packages/` * Include paths: `project-a/*, packages/*` * Exclude paths: \`\` ### Example 2 If you want to trigger a build for any changes, but want to exclude changes to a certain directory, such as all changes in a docs/ directory * Include paths: `*` * Exclude paths: `docs/*` ### Example 3 If you want to trigger a build for a specific file or specific filetype, for example all files ending in `.md`. * Include paths: `*.md` * Exclude paths: \`\` ```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/ci-cd/","name":"CI/CD"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/ci-cd/builds/","name":"Builds"}},{"@type":"ListItem","position":5,"item":{"@id":"/workers/ci-cd/builds/build-watch-paths/","name":"Build watch paths"}}]} ``` --- --- title: Configuration description: Understand the different settings associated with your build. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/ci-cd/builds/configuration.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Configuration When connecting your Git repository to your Worker, you can customize the configurations needed to build and deploy your Worker. ## How Workers Builds works When a commit is pushed to your connected repository, Workers Builds runs a two-step process: 1. **Build command** _(optional)_ \- Compiles your project (for example, `npm run build` for frameworks like Next.js or Astro) 2. **Deploy command** \- Deploys your Worker to Cloudflare (defaults to `npx wrangler deploy`) For preview builds (commits to branches other than your production branch), the deploy command is replaced with a **preview deploy command** (defaults to `npx wrangler versions upload`), which creates a preview version without promoting it to production. ## Build settings Build settings can be found by navigating to **Settings** \> **Build** within your Worker. Note that when you update and save build settings, the updated settings will be applied to your _next_ build. When you _retry_ a build, the build configurations that exist when the build is retried will be applied. ### Overview | Setting | Description | | ------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Git account** | Select the Git account you would like to use. After the initial connection, you can continue to use this Git account for future projects. | | **Git repository** | Choose the Git repository you would like to connect your Worker to. | | **Git branch** | Select the branch you would like Cloudflare to listen to for new commits. This will be defaulted to main. | | **Build command** _(Optional)_ | Set a build command if your project requires a build step (e.g. npm run build). This is necessary, for example, when using a [front-end framework](https://developers.cloudflare.com/workers/ci-cd/builds/configuration/#framework-support) such as Next.js or Remix. | | **[Deploy command](https://developers.cloudflare.com/workers/ci-cd/builds/configuration/#deploy-command)** | The deploy command lets you set the [specific Wrangler command](https://developers.cloudflare.com/workers/wrangler/commands/general/#deploy) used to deploy your Worker. Your deploy command will default to npx wrangler deploy but you may customize this command. Workers Builds will use the Wrangler version set in your package json. | | **[Non-production branch deploy command](https://developers.cloudflare.com/workers/ci-cd/builds/configuration/#non-production-branch-deploy-command)** | Set a command to run when executing [a build for commit on a non-production branch](https://developers.cloudflare.com/workers/ci-cd/builds/build-branches/#configure-non-production-branch-builds). This will default to npx wrangler versions upload but you may customize this command. Workers Builds will use the Wrangler version set in your package json. | | **Root directory** _(Optional)_ | Specify the path to your project. The root directory defines where the build command will be run and can be helpful in [monorepos](https://developers.cloudflare.com/workers/ci-cd/builds/advanced-setups/#monorepos) to isolate a specific project within the repository for builds. | | **[API token](https://developers.cloudflare.com/workers/ci-cd/builds/configuration/#api-token)** _(Optional)_ | The API token is used to authenticate your build request and authorize the upload and deployment of your Worker to Cloudflare. By default, Cloudflare will automatically generate an API token for your account when using Workers Builds, and continue to use this API token for all subsequent builds. Alternatively, you can [create your own API token](https://developers.cloudflare.com/workers/wrangler/migration/v1-to-v2/wrangler-legacy/authentication/#generate-tokens), or select one that you already own. | | **Build variables and secrets** _(Optional)_ | Add environment variables and secrets accessible only to your build. Build variables will not be accessible at runtime. If you would like to configure runtime variables you can do so in **Settings** \> **Variables & Secrets** | Note Currently, Workers Builds does not honor the configurations set in [Custom Builds](https://developers.cloudflare.com/workers/wrangler/custom-builds/) within your Wrangler configuration file. ### Deploy command You can run your deploy command using the package manager of your choice. If you have added a Wrangler deploy command as a script in your `package.json`, then you can run it by setting it as your deploy command. For example, `npm run deploy`. Examples of other deploy commands you can set include: | Example Command | Description | | -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | npx wrangler deploy --assets ./public/ | Deploy your Worker along with static assets from the specified directory. Alternatively, you can use the [assets binding](https://developers.cloudflare.com/workers/static-assets/binding/). | | npx wrangler deploy --env staging | If you have a [Wrangler environment](https://developers.cloudflare.com/workers/ci-cd/builds/advanced-setups/#wrangler-environments) Worker, you should set your deploy command with the environment flag. For more details, see [Advanced Setups](https://developers.cloudflare.com/workers/ci-cd/builds/advanced-setups/#wrangler-environments). | ### Non-production branch deploy command The non-production branch deploy command is only applicable when you have enabled [non-production branch builds](https://developers.cloudflare.com/workers/ci-cd/builds/build-branches/#configure-non-production-branch-builds). It defaults to `npx wrangler versions upload`, producing a [preview URL](https://developers.cloudflare.com/workers/configuration/previews/). Like the build and deploy commands, it can be customized to instead run anything. Examples of other non-production branch deploy commands you can set include: | Example Command | Description | | ------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | yarn exec wrangler versions upload | You can customize the package manager used to run Wrangler. | | npx wrangler versions upload --env staging | If you have a [Wrangler environment](https://developers.cloudflare.com/workers/ci-cd/builds/advanced-setups/#wrangler-environments) Worker, you should set your non-production branch deploy command with the environment flag. For more details, see [Advanced Setups](https://developers.cloudflare.com/workers/ci-cd/builds/advanced-setups/#wrangler-environments). | ### Automatic configuration for new projects If your repository does not have a Wrangler configuration file, the deploy command (`wrangler deploy`) will trigger [automatic project configuration](https://developers.cloudflare.com/workers/framework-guides/automatic-configuration/). This detects your framework, creates the necessary configuration, and opens a [pull request](https://developers.cloudflare.com/workers/ci-cd/builds/automatic-prs/) for you to review. Once you merge the PR, your project is configured and future builds will deploy normally. ### API token The API token in Workers Builds defines the access granted to Workers Builds for interacting with your account's resources. Currently, only user tokens are supported, with account-owned token support coming soon. When you select **Create new token**, a new API token will be created automatically with the following permissions: * **Account:** Account Settings (read), Workers Scripts (edit), Workers KV Storage (edit), Workers R2 Storage (edit) * **Zone:** Workers Routes (edit) for all zones on the account * **User:** User Details (read), Memberships (read) You can configure the permissions of this API token by navigating to **My Profile** \> **API Tokens** for user tokens. It is recommended to consistently use the same API token across all uploads and deployments of your Worker to maintain consistent access permissions. ## Framework support [Static assets](https://developers.cloudflare.com/workers/static-assets/) and [frameworks](https://developers.cloudflare.com/workers/framework-guides/) are now supported in Cloudflare Workers. Learn to set up Workers projects and the commands for each framework in the framework guides: * [ AI & agents ](https://developers.cloudflare.com/workers/framework-guides/ai-and-agents/) * [ Agents SDK ](https://developers.cloudflare.com/agents/) * [ LangChain ](https://developers.cloudflare.com/workers/languages/python/packages/langchain/) * [ APIs ](https://developers.cloudflare.com/workers/framework-guides/apis/) * [ FastAPI ](https://developers.cloudflare.com/workers/languages/python/packages/fastapi/) * [ Hono ](https://developers.cloudflare.com/workers/framework-guides/web-apps/more-web-frameworks/hono/) * [ Deploy an existing project ](https://developers.cloudflare.com/workers/framework-guides/automatic-configuration/) * [ Mobile applications ](https://developers.cloudflare.com/workers/framework-guides/mobile-apps/) * [ Expo ](https://docs.expo.dev/eas/hosting/reference/worker-runtime/) * [ Web applications ](https://developers.cloudflare.com/workers/framework-guides/web-apps/) * [ React + Vite ](https://developers.cloudflare.com/workers/framework-guides/web-apps/react/) * [ Astro ](https://developers.cloudflare.com/workers/framework-guides/web-apps/astro/) * [ React Router (formerly Remix) ](https://developers.cloudflare.com/workers/framework-guides/web-apps/react-router/) * [ Vue ](https://developers.cloudflare.com/workers/framework-guides/web-apps/vue/) * [ TanStack Start ](https://developers.cloudflare.com/workers/framework-guides/web-apps/tanstack-start/) * [ Microfrontends ](https://developers.cloudflare.com/workers/framework-guides/web-apps/microfrontends/) * [ More guides... ](https://developers.cloudflare.com/workers/framework-guides/web-apps/more-web-frameworks/) * [ Analog ](https://developers.cloudflare.com/workers/framework-guides/web-apps/more-web-frameworks/analog/) * [ Angular ](https://developers.cloudflare.com/workers/framework-guides/web-apps/more-web-frameworks/angular/) * [ Docusaurus ](https://developers.cloudflare.com/workers/framework-guides/web-apps/more-web-frameworks/docusaurus/) * [ Gatsby ](https://developers.cloudflare.com/workers/framework-guides/web-apps/more-web-frameworks/gatsby/) * [ Hono ](https://developers.cloudflare.com/workers/framework-guides/web-apps/more-web-frameworks/hono/) * [ Nuxt ](https://developers.cloudflare.com/workers/framework-guides/web-apps/more-web-frameworks/nuxt/) * [ Qwik ](https://developers.cloudflare.com/workers/framework-guides/web-apps/more-web-frameworks/qwik/) * [ Solid ](https://developers.cloudflare.com/workers/framework-guides/web-apps/more-web-frameworks/solid/) * [ Waku ](https://developers.cloudflare.com/workers/framework-guides/web-apps/more-web-frameworks/waku/) * [ Next.js ](https://developers.cloudflare.com/workers/framework-guides/web-apps/nextjs/) * [ RedwoodSDK ](https://developers.cloudflare.com/workers/framework-guides/web-apps/redwoodsdk/) * [ SvelteKit ](https://developers.cloudflare.com/workers/framework-guides/web-apps/sveltekit/) * [ Vike ](https://developers.cloudflare.com/workers/framework-guides/web-apps/vike/) ## Environment variables You can provide custom environment variables to your build. * [ Dashboard ](#tab-panel-7210) * [ Wrangler ](#tab-panel-7211) To add environment variables via the dashboard: 1. In the Cloudflare dashboard, go to the **Workers & Pages** page. [ Go to **Workers & Pages** ](https://dash.cloudflare.com/?to=/:account/workers-and-pages) 1. In **Overview**, select your Worker. 2. Select **Settings** \> **Environment variables**. To add env variables using Wrangler, define text and JSON via the `[vars]` configuration in your Wrangler file. * [ wrangler.jsonc ](#tab-panel-7208) * [ wrangler.toml ](#tab-panel-7209) JSONC ``` { "$schema": "./node_modules/wrangler/config-schema.json", "name": "my-worker-dev", "vars": { "API_HOST": "example.com", "API_ACCOUNT_ID": "example_user", "SERVICE_X_DATA": { "URL": "service-x-api.dev.example", "MY_ID": 123 } } } ``` Explain Code TOML ``` "$schema" = "./node_modules/wrangler/config-schema.json" name = "my-worker-dev" [vars] API_HOST = "example.com" API_ACCOUNT_ID = "example_user" [vars.SERVICE_X_DATA] URL = "service-x-api.dev.example" MY_ID = 123 ``` Explain Code ### Default variables The following system environment variables are injected by default (but can be overridden): | Environment Variable | Injected value | Example use-case | | ------------------------ | ----------------------------- | ------------------------------------------------------------------------------------- | | CI | true | Changing build behaviour when run on CI versus locally | | WORKERS\_CI | 1 | Changing build behaviour when run on Workers Builds versus locally | | WORKERS\_CI\_BUILD\_UUID | | Passing the Build UUID along to custom workflows | | WORKERS\_CI\_COMMIT\_SHA | | Passing current commit ID to error reporting, for example, Sentry | | WORKERS\_CI\_BRANCH | **Builds** \> **Deploy Hooks**. 3. Enter a **name** and select the **branch** to build. 4. Select **Create** and copy the generated URL. Note Give each Deploy Hook a descriptive name so you can tell them apart. If you have multiple content sources that each need to trigger builds independently, create a separate hook for each one. ## Trigger a Deploy Hook Send an HTTP POST request to your Deploy Hook URL to start a build: Terminal window ``` curl -X POST "https://api.cloudflare.com/client/v4/workers/builds/deploy_hooks/" ``` No `Authorization` header is needed. The unique identifier embedded in the URL acts as the authentication credential. Example response: ``` { "success": true, "errors": [], "messages": [], "result": { "build_uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "branch": "main", "worker": "my-worker" } } ``` Explain Code The `build_uuid` in the response can be used to [monitor build status and retrieve logs](https://developers.cloudflare.com/workers/ci-cd/builds/api-reference/#get-build-logs). ### Verify the build After you trigger a Deploy Hook, you can verify it from the dashboard: * In the **Deploy Hooks** list, the hook shows when it was last triggered. * In your Worker's build history, the **Triggered by** column identifies builds started by a Deploy Hook using the hook name and a `deploy hook` label. If you need to inspect these builds programmatically, use [List builds for a Worker](https://developers.cloudflare.com/workers/ci-cd/builds/api-reference/#list-builds-for-a-worker) in the Builds API reference. Hook-triggered builds are recorded with `build_trigger_source: "deploy_hook"`. ## CMS integration Most headless CMS platforms support webhooks that call your Deploy Hook URL when content changes. The general setup is the same across platforms: 1. Find the webhooks or integrations settings in your CMS. 2. Create a new webhook and paste your Deploy Hook URL as the target URL. 3. Select which events should trigger the webhook (for example, publish, unpublish, or update). Refer to your CMS documentation for platform-specific instructions. Popular platforms with webhook support include Contentful, Sanity, Strapi, Storyblok, DatoCMS, and Prismic. ## Idempotency If the same Deploy Hook is triggered again before the previous build has fully started, Workers Builds does not create a duplicate build. Instead, it returns the build that is already in progress. If an external system sends the same Deploy Hook twice in quick succession: 1. The first request creates a build. 2. If a second request arrives while that build is still `queued` or `initializing`, no second build is created. 3. Instead, the response returns the existing `build_uuid` and sets `already_exists` to `true`. Example response when an existing pending build is returned: ``` { "success": true, "errors": [], "messages": [], "result": { "build_uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "status": "queued", "created_on": "2026-01-21T18:50:00Z", "already_exists": true } } ``` Explain Code Once the earlier build moves past `initializing`, a later POST creates a new build as normal. This makes Deploy Hooks safe to use with systems that retry webhooks or emit bursts of content-update events. ## Examples ### Deploy from a Slack slash command A Worker that receives a `/deploy` command from Slack and triggers a build: * [ JavaScript ](#tab-panel-7214) * [ TypeScript ](#tab-panel-7215) JavaScript ``` export default { async fetch(request, env) { const body = await request.formData(); const command = body.get("command"); const token = body.get("token"); if (token !== env.SLACK_VERIFICATION_TOKEN) { return new Response("Unauthorized", { status: 401 }); } if (command === "/deploy") { const res = await fetch(env.DEPLOY_HOOK_URL, { method: "POST" }); const { result } = await res.json(); return new Response(`Build started: ${result.build_uuid}`); } return new Response("Unknown command", { status: 400 }); }, }; ``` Explain Code TypeScript ``` export default { async fetch(request: Request, env: Env): Promise { const body = await request.formData(); const command = body.get("command"); const token = body.get("token"); if (token !== env.SLACK_VERIFICATION_TOKEN) { return new Response("Unauthorized", { status: 401 }); } if (command === "/deploy") { const res = await fetch(env.DEPLOY_HOOK_URL, { method: "POST" }); const { result } = await res.json<{ result: { build_uuid: string } }>(); return new Response(`Build started: ${result.build_uuid}`); } return new Response("Unknown command", { status: 400 }); }, }; ``` Explain Code ### Rebuild on a schedule A Worker with a [Cron Trigger](https://developers.cloudflare.com/workers/configuration/cron-triggers/) that rebuilds every hour: * [ JavaScript ](#tab-panel-7212) * [ TypeScript ](#tab-panel-7213) JavaScript ``` export default { async scheduled(event, env) { await fetch(env.DEPLOY_HOOK_URL, { method: "POST" }); }, }; ``` TypeScript ``` export default { async scheduled(event: ScheduledEvent, env: Env): Promise { await fetch(env.DEPLOY_HOOK_URL, { method: "POST" }); }, }; ``` ## Security considerations Warning Deploy Hook URLs do not require a separate authorization header. Anyone with access to the URL can trigger builds for your Worker, so store them like other sensitive credentials. * Store Deploy Hook URLs in environment variables or a secrets manager, never in source code or public configuration files. * Restrict access to the URL to only the systems that need it. * If a URL is compromised or you suspect unauthorized use, delete the Deploy Hook immediately and create a new one. The old URL stops working as soon as it is deleted. ### Using the Builds API for authenticated triggers If your external system supports custom headers, you can call the [manual build endpoint](https://developers.cloudflare.com/api/resources/workers%5Fbuilds/subresources/triggers/methods/create%5Fbuild) with an API token in the `Authorization` header instead. This gives you token-based authentication and the ability to choose the branch per request. For a step-by-step walkthrough, see [Trigger a manual build](https://developers.cloudflare.com/workers/ci-cd/builds/api-reference/#trigger-a-manual-build). ## Limits Deploy Hooks are rate limited to 10 builds per minute per Worker and 100 builds per minute per account. For all Workers Builds limits, see [Limits & pricing](https://developers.cloudflare.com/workers/ci-cd/builds/limits-and-pricing/). ```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/ci-cd/","name":"CI/CD"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/ci-cd/builds/","name":"Builds"}},{"@type":"ListItem","position":5,"item":{"@id":"/workers/ci-cd/builds/deploy-hooks/","name":"Deploy Hooks"}}]} ``` --- --- title: Event subscriptions description: Event subscriptions allow you to receive messages when events occur across your Cloudflare account. Cloudflare products (e.g., KV, Workers AI, Workers) can publish structured events to a queue, which you can then consume with Workers or HTTP pull consumers to build custom workflows, integrations, or logic. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/ci-cd/builds/event-subscriptions.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Event subscriptions [Event subscriptions](https://developers.cloudflare.com/queues/event-subscriptions/) allow you to receive messages when events occur across your Cloudflare account. Cloudflare products (e.g., [KV](https://developers.cloudflare.com/kv/), [Workers AI](https://developers.cloudflare.com/workers-ai/), [Workers](https://developers.cloudflare.com/workers/)) can publish structured events to a [queue](https://developers.cloudflare.com/queues/), which you can then consume with Workers or [HTTP pull consumers](https://developers.cloudflare.com/queues/configuration/pull-consumers/) to build custom workflows, integrations, or logic. For more information on [Event Subscriptions](https://developers.cloudflare.com/queues/event-subscriptions/), refer to the [management guide](https://developers.cloudflare.com/queues/event-subscriptions/manage-event-subscriptions/). ## Send build notifications You can deploy a Worker that consumes build events and sends notifications to Slack, Discord, or any webhook endpoint: [![Deploy to Cloudflare](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/cloudflare/templates/tree/main/workers-builds-notifications-template) The template sends notifications for: * Successful builds with preview or live deployment URLs * Failed builds with error messages * Cancelled builds ![Example Slack notifications for Workers Builds events](https://developers.cloudflare.com/_astro/builds-notifications-slack.rcRiU95L_169ufw.webp) You can customize the Worker to format messages for your webhook provider. For setup instructions, refer to the [template README ↗](https://github.com/cloudflare/templates/tree/main/workers-builds-notifications-template#readme). ## Available Workers Builds events #### `build.started` Triggered when a build starts. **Example:** ``` { "type": "cf.workersBuilds.worker.build.started", "source": { "type": "workersBuilds.worker", "workerName": "my-worker" }, "payload": { "buildUuid": "build-12345678-90ab-cdef-1234-567890abcdef", "status": "running", "buildOutcome": null, "createdAt": "2025-05-01T02:48:57.132Z", "initializingAt": "2025-05-01T02:48:58.132Z", "runningAt": "2025-05-01T02:48:59.132Z", "stoppedAt": null, "buildTriggerMetadata": { "buildTriggerSource": "push_event", "branch": "main", "commitHash": "abc123def456", "commitMessage": "Fix bug in authentication", "author": "developer@example.com", "buildCommand": "npm run build", "deployCommand": "wrangler deploy", "rootDirectory": "/", "repoName": "my-worker-repo", "providerAccountName": "github-user", "providerType": "github" } }, "metadata": { "accountId": "f9f79265f388666de8122cfb508d7776", "eventSubscriptionId": "1830c4bb612e43c3af7f4cada31fbf3f", "eventSchemaVersion": 1, "eventTimestamp": "2025-05-01T02:48:57.132Z" } } ``` Explain Code #### `build.failed` Triggered when a build fails. **Example:** ``` { "type": "cf.workersBuilds.worker.build.failed", "source": { "type": "workersBuilds.worker", "workerName": "my-worker" }, "payload": { "buildUuid": "build-12345678-90ab-cdef-1234-567890abcdef", "status": "failed", "buildOutcome": "failure", "createdAt": "2025-05-01T02:48:57.132Z", "initializingAt": "2025-05-01T02:48:58.132Z", "runningAt": "2025-05-01T02:48:59.132Z", "stoppedAt": "2025-05-01T02:50:00.132Z", "buildTriggerMetadata": { "buildTriggerSource": "push_event", "branch": "main", "commitHash": "abc123def456", "commitMessage": "Fix bug in authentication", "author": "developer@example.com", "buildCommand": "npm run build", "deployCommand": "wrangler deploy", "rootDirectory": "/", "repoName": "my-worker-repo", "providerAccountName": "github-user", "providerType": "github" } }, "metadata": { "accountId": "f9f79265f388666de8122cfb508d7776", "eventSubscriptionId": "1830c4bb612e43c3af7f4cada31fbf3f", "eventSchemaVersion": 1, "eventTimestamp": "2025-05-01T02:48:57.132Z" } } ``` Explain Code #### `build.canceled` Triggered when a build is canceled. **Example:** ``` { "type": "cf.workersBuilds.worker.build.canceled", "source": { "type": "workersBuilds.worker", "workerName": "my-worker" }, "payload": { "buildUuid": "build-12345678-90ab-cdef-1234-567890abcdef", "status": "canceled", "buildOutcome": "canceled", "createdAt": "2025-05-01T02:48:57.132Z", "initializingAt": "2025-05-01T02:48:58.132Z", "runningAt": "2025-05-01T02:48:59.132Z", "stoppedAt": "2025-05-01T02:49:30.132Z", "buildTriggerMetadata": { "buildTriggerSource": "push_event", "branch": "main", "commitHash": "abc123def456", "commitMessage": "Fix bug in authentication", "author": "developer@example.com", "buildCommand": "npm run build", "deployCommand": "wrangler deploy", "rootDirectory": "/", "repoName": "my-worker-repo", "providerAccountName": "github-user", "providerType": "github" } }, "metadata": { "accountId": "f9f79265f388666de8122cfb508d7776", "eventSubscriptionId": "1830c4bb612e43c3af7f4cada31fbf3f", "eventSchemaVersion": 1, "eventTimestamp": "2025-05-01T02:48:57.132Z" } } ``` Explain Code #### `build.succeeded` Triggered when a build succeeds. **Example:** ``` { "type": "cf.workersBuilds.worker.build.succeeded", "source": { "type": "workersBuilds.worker", "workerName": "my-worker" }, "payload": { "buildUuid": "build-12345678-90ab-cdef-1234-567890abcdef", "status": "success", "buildOutcome": "success", "createdAt": "2025-05-01T02:48:57.132Z", "initializingAt": "2025-05-01T02:48:58.132Z", "runningAt": "2025-05-01T02:48:59.132Z", "stoppedAt": "2025-05-01T02:50:15.132Z", "buildTriggerMetadata": { "buildTriggerSource": "push_event", "branch": "main", "commitHash": "abc123def456", "commitMessage": "Fix bug in authentication", "author": "developer@example.com", "buildCommand": "npm run build", "deployCommand": "wrangler deploy", "rootDirectory": "/", "repoName": "my-worker-repo", "providerAccountName": "github-user", "providerType": "github" } }, "metadata": { "accountId": "f9f79265f388666de8122cfb508d7776", "eventSubscriptionId": "1830c4bb612e43c3af7f4cada31fbf3f", "eventSchemaVersion": 1, "eventTimestamp": "2025-05-01T02:48:57.132Z" } } ``` Explain Code ```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/ci-cd/","name":"CI/CD"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/ci-cd/builds/","name":"Builds"}},{"@type":"ListItem","position":5,"item":{"@id":"/workers/ci-cd/builds/event-subscriptions/","name":"Event subscriptions"}}]} ``` --- --- title: Git integration description: Learn how to add and manage your Git integration for Workers Builds image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/ci-cd/builds/git-integration/index.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Git integration Cloudflare supports connecting your [GitHub](https://developers.cloudflare.com/workers/ci-cd/builds/git-integration/github-integration/) and [GitLab](https://developers.cloudflare.com/workers/ci-cd/builds/git-integration/gitlab-integration/) repository to your Cloudflare Worker, and will automatically deploy your code every time you push a change. Adding a Git integration also lets you monitor build statuses directly in your Git provider using [pull request comments](https://developers.cloudflare.com/workers/ci-cd/builds/git-integration/github-integration/#pull-request-comment), [check runs](https://developers.cloudflare.com/workers/ci-cd/builds/git-integration/github-integration/#check-run), or [commit statuses](https://developers.cloudflare.com/workers/ci-cd/builds/git-integration/gitlab-integration/#commit-status), so you can manage deployments without leaving your workflow. ## Supported Git Providers Cloudflare supports connecting Cloudflare Workers to your GitHub and GitLab repositories. Workers Builds does not currently support connecting self-hosted instances of GitHub or GitLab. If you using a different Git provider (e.g. Bitbucket), you can use an [external CI/CD provider (e.g. GitHub Actions)](https://developers.cloudflare.com/workers/ci-cd/external-cicd/) and deploy using [Wrangler CLI](https://developers.cloudflare.com/workers/wrangler/commands/general/#deploy). ## Add a Git Integration Workers Builds provides direct integration with GitHub and GitLab accounts, including both individual and organization accounts, that are _not_ self-hosted. If you do not have a Git account linked to your Cloudflare account, you will be prompted to set up an installation to GitHub or GitLab when [connecting a repository](https://developers.cloudflare.com/workers/ci-cd/builds/#get-started) for the first time, or when adding a new Git account. Follow the prompts and authorize the Cloudflare Git integration. ![Git providers](https://developers.cloudflare.com/_astro/workers-git-provider.aIMoWcJE_Z1X4wCk.webp) You can check the following pages to see if your Git integration has been installed: * [GitHub Applications page ↗](https://github.com/settings/installations) (if you are in an organization, select **Switch settings context** to access your GitHub organization settings) * [GitLab Authorized Applications page ↗](https://gitlab.com/-/profile/applications) For details on providing access to organization accounts, see [GitHub organizational access](https://developers.cloudflare.com/workers/ci-cd/builds/git-integration/github-integration/#organizational-access) and [GitLab organizational access](https://developers.cloudflare.com/workers/ci-cd/builds/git-integration/gitlab-integration/#organizational-access). ## Manage a Git Integration To manage your Git installation: 1. Go to the **Workers & Pages** page in the Cloudflare dashboard. [ Go to **Workers & Pages** ](https://dash.cloudflare.com/?to=/:account/workers-and-pages) 2. Select your Worker. 3. Go to **Settings** \> **Builds**. 4. Under **Git Repository**, select **Manage**. This can be useful for managing repository access or troubleshooting installation issues by reinstalling. For more details, see the [GitHub](https://developers.cloudflare.com/workers/ci-cd/builds/git-integration/github-integration) and [GitLab](https://developers.cloudflare.com/workers/ci-cd/builds/git-integration/gitlab-integration) guides for how to manage your installation. ```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/ci-cd/","name":"CI/CD"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/ci-cd/builds/","name":"Builds"}},{"@type":"ListItem","position":5,"item":{"@id":"/workers/ci-cd/builds/git-integration/","name":"Git integration"}}]} ``` --- --- title: GitHub integration description: Learn how to manage your GitHub integration for Workers Builds image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/ci-cd/builds/git-integration/github-integration.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # GitHub integration Cloudflare supports connecting your GitHub repository to your Cloudflare Worker, and will automatically deploy your code every time you push a change. ## Features Beyond automatic builds and deployments, the Cloudflare GitHub integration lets you monitor builds directly in GitHub, keeping you informed without leaving your workflow. ### Pull request comment If a commit is on a pull request, Cloudflare will automatically post a comment on the pull request with the status of the build. ![GitHub pull request comment](https://developers.cloudflare.com/_astro/github-pull-request-comment.DIkAC8Yh_yF45V.webp) A [preview URL](https://developers.cloudflare.com/workers/configuration/previews/) will be provided for any builds which perform `wrangler versions upload`. This is particularly useful when reviewing your pull request, as it allows you to compare the code changes alongside an updated version of your Worker. Comment history reveals any builds completed earlier while the PR was open. ![GitHub pull request comment history](https://developers.cloudflare.com/_astro/github-pull-request-comment-history.pAxP7K1u_Z2jBa6y.webp) ### Check run If you have one or multiple Workers connected to a repository (i.e. a [monorepo](https://developers.cloudflare.com/workers/ci-cd/builds/advanced-setups/#monorepos)), you can check on the status of each build within GitHub via [GitHub check runs ↗](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/collaborating-on-repositories-with-code-quality-features/about-status-checks#checks). You can see the checks by selecting on the status icon next to a commit within your GitHub repository. In the example below, you can select the green check mark to see the results of the check run. ![GitHub status](https://developers.cloudflare.com/_astro/gh-status-check-runs.DkY_pO9C_1Obpz1.webp) Check runs will appear like the following in your repository. You can select **Details** to view the build (Build ID) and project (Script) associated with each check. ![GitHub check runs](https://developers.cloudflare.com/_astro/workers-builds-gh-check-runs.CuqL6Htu_Z1vG6k.webp) Note that when using [build watch paths](https://developers.cloudflare.com/workers/ci-cd/builds/build-watch-paths/), only projects that trigger a build will generate a check run. ## Manage access You can deploy projects to Cloudflare Workers from your company or side project on GitHub using the [Cloudflare Workers & Pages GitHub App ↗](https://github.com/apps/cloudflare-workers-and-pages). ### Organizational access When authorizing Cloudflare Workers to access a GitHub account, you can specify access to your individual account or an organization that you belong to on GitHub. To add Cloudflare Workers installation to an organization, your user account must be an owner or have the appropriate role within the organization (i.e. the GitHub Apps Manager role). More information on these roles can be seen on [GitHub's documentation ↗](https://docs.github.com/en/organizations/managing-peoples-access-to-your-organization-with-roles/roles-in-an-organization#github-app-managers). GitHub security consideration A GitHub account should only point to one Cloudflare account. If you are setting up Cloudflare with GitHub for your organization, Cloudflare recommends that you limit the scope of the application to only the repositories you intend to build with Pages. To modify these permissions, go to the [Applications page ↗](https://github.com/settings/installations) on GitHub and select **Switch settings context** to access your GitHub organization settings. Then, select **Cloudflare Workers & Pages** \> For **Repository access**, select **Only select repositories** \> select your repositories. ### Remove access You can remove Cloudflare Workers' access to your GitHub repository or account by going to the [Applications page ↗](https://github.com/settings/installations) on GitHub (if you are in an organization, select Switch settings context to access your GitHub organization settings). The GitHub App is named Cloudflare Workers and Pages, and it is shared between Workers and Pages projects. #### Remove Cloudflare access to a GitHub repository To remove access to an individual GitHub repository, you can navigate to **Repository access**. Select the **Only select repositories** option, and configure which repositories you would like Cloudflare to have access to. ![GitHub Repository Access](https://developers.cloudflare.com/_astro/github-repository-access.DGHekBft_ZyV5F2.webp) #### Remove Cloudflare access to the entire GitHub account To remove Cloudflare Workers and Pages access to your entire Git account, you can navigate to **Uninstall "Cloudflare Workers and Pages"**, then select **Uninstall**. Removing access to the Cloudflare Workers and Pages app will revoke Cloudflare's access to _all repositories_ from that GitHub account. If you want to only disable automatic builds and deployments, follow the [Disable Build](https://developers.cloudflare.com/workers/ci-cd/builds/#disconnecting-builds) instructions. Note that removing access to GitHub will disable new builds for Workers and Pages project that were connected to those repositories, though your previous deployments will continue to be hosted by Cloudflare Workers. ### Reinstall the Cloudflare GitHub App When encountering Git integration related issues, one potential troubleshooting step is attempting to uninstall and reinstall the GitHub or GitLab application associated with the Cloudflare Pages installation. The process for each Git provider is provided below. 1. Go to the installation settings page on GitHub: * Navigate to **Settings > Builds** for the Workers or Pages project and select **Manage** under Git Repository. * Alternatively, visit these links to find the Cloudflare Workers and Pages installation and select **Configure**: | **Individual** | https://github.com/settings/installations | | ---------------- | ---------------------------------------------------------------------------------- | | **Organization** | https://github.com/organizations//settings/installations | 1. In the Cloudflare Workers and Pages GitHub App settings page, navigate to **Uninstall "Cloudflare Workers and Pages"** and select **Uninstall**. 2. Go back to the [**Workers & Pages** overview ↗](https://dash.cloudflare.com) page. Select **Create application** \> **Pages** \> **Connect to Git**. 3. Select the **\+ Add account** button, select the GitHub account you want to add, and then select **Install & Authorize**. 4. You should be redirected to the create project page with your GitHub account or organization in the account list. 5. Attempt to make a new deployment with your project which was previously broken. ```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/ci-cd/","name":"CI/CD"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/ci-cd/builds/","name":"Builds"}},{"@type":"ListItem","position":5,"item":{"@id":"/workers/ci-cd/builds/git-integration/","name":"Git integration"}},{"@type":"ListItem","position":6,"item":{"@id":"/workers/ci-cd/builds/git-integration/github-integration/","name":"GitHub integration"}}]} ``` --- --- title: GitLab integration description: Learn how to manage your GitLab integration for Workers Builds image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/ci-cd/builds/git-integration/gitlab-integration.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # GitLab integration Cloudflare supports connecting your GitLab repository to your Cloudflare Worker, and will automatically deploy your code every time you push a change. ## Features Beyond automatic builds and deployments, the Cloudflare GitLab integration lets you monitor builds directly in GitLab, keeping you informed without leaving your workflow. ### Merge request comment If a commit is on a merge request, Cloudflare will automatically post a comment on the merge request with the status of the build. ![GitLab merge request comment](https://developers.cloudflare.com/_astro/gitlab-pull-request-comment.CQVsQ21r_jud8J.webp) A [preview URL](https://developers.cloudflare.com/workers/configuration/previews/) will be provided for any builds which perform `wrangler versions upload`. This is particularly useful when reviewing your pull request, as it allows you to compare the code changes alongside an updated version of your Worker. Enabling GitLab Merge Request events for existing connections New GitLab connections are automatically configured to receive merge request events, which enable commenting functionality. For existing connections, you'll need to manually enable `Merge request events` in the Webhooks tab of your project's settings. You can follow GitLab's documentation for guidance on [managing webhooks ↗](https://docs.gitlab.com/user/project/integrations/webhooks/#manage-webhooks). ### Commit Status If you have one or multiple Workers connected to a repository (i.e. a [monorepo](https://developers.cloudflare.com/workers/ci-cd/builds/advanced-setups/#monorepos)), you can check on the status of each build within GitLab via [GitLab commit status ↗](https://docs.gitlab.com/ee/user/project/merge%5Frequests/status%5Fchecks.html). You can see the statuses by selecting the status icon next to a commit or by going to **Build** \> **Pipelines** within your GitLab repository. In the example below, you can select on the green check mark to see the results of the check run. ![GitLab Status](https://developers.cloudflare.com/_astro/gl-status-checks.B9jgSbf7_Z1XRFYR.webp) Check runs will appear like the following in your repository. You can select one of the statuses to view the build on the Cloudflare Dashboard. ![GitLab Commit Status](https://developers.cloudflare.com/_astro/gl-commit-status.BghMWpYX_Za7rrg.webp) Note that when using [build watch paths](https://developers.cloudflare.com/workers/ci-cd/builds/build-watch-paths/), only projects that trigger a build will generate a commit status. ## Manage access You can deploy projects to Cloudflare Workers from your company or side project on GitLab using the Cloudflare Pages app. ### Organizational access When you authorize Cloudflare Workers to access your GitLab account, you automatically give Cloudflare Workers access to organizations, groups, and namespaces accessed by your GitLab account. Managing access to these organizations and groups is handled by GitLab. ### Remove access You can remove Cloudflare Workers' access to your GitLab account by navigating to [Authorized Applications page ↗](https://gitlab.com/-/profile/applications) on GitLab. Find the applications called Cloudflare Pages and select the **Revoke** button to revoke access. Note that the GitLab application Cloudflare Workers is shared between Workers and Pages projects, and removing access to GitLab will disable new builds for Workers and Pages, though your previous deployments will continue to be hosted by Cloudflare Workers. ### Reinstall the Cloudflare GitLab App 1. Go to your application settings page on GitLab: [https://gitlab.com/-/profile/applications ↗](https://gitlab.com/-/profile/applications) 2. Click the "Revoke" button on your Cloudflare Workers installation if it exists. 3. Go back to the [**Workers & Pages** overview ↗](https://dash.cloudflare.com) page. Select **Create application** \> **Pages** \> **Connect to Git**. 4. Select the **\+ Add account** button, select the GitLab account you want to add, and then select **Install & Authorize**. 5. You should be redirected to the create project page with your GitLab account or organization in the account list. 6. Attempt to make a new deployment with your project which was previously broken. ```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/ci-cd/","name":"CI/CD"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/ci-cd/builds/","name":"Builds"}},{"@type":"ListItem","position":5,"item":{"@id":"/workers/ci-cd/builds/git-integration/","name":"Git integration"}},{"@type":"ListItem","position":6,"item":{"@id":"/workers/ci-cd/builds/git-integration/gitlab-integration/","name":"GitLab integration"}}]} ``` --- --- title: Limits & pricing description: Limits & pricing for Workers Builds image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/ci-cd/builds/limits-and-pricing.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Limits & pricing Workers Builds has the following limits. | Metric | Free plan | Paid plans | | --------------------------------- | -------------------------------------- | ------------------------------------------ | | **Build minutes** | 3,000 per month | 6,000 per month (then, +$0.005 per minute) | | **Concurrent builds** | 1 | 6 | | **Build timeout** | 20 minutes | 20 minutes | | **Deploy Hooks** | 10/min per Worker, 100/min per account | 10/min per Worker, 100/min per account | | **CPU** | 2 vCPU | 4 vCPU | | **Memory** | 8 GB | 8 GB | | **Disk space** | 20 GB | 20 GB | | **Environment variables** | 64 | 64 | | **Size per environment variable** | 5 KB | 5 KB | ## Definitions * **Build minutes**: The amount of minutes that it takes to build a project. * **Concurrent builds**: The number of builds that can run in parallel across an account. * **Build timeout**: The amount of time that a build can be run before it is terminated. * **Deploy Hooks**: The rate limit for builds triggered by [Deploy Hooks](https://developers.cloudflare.com/workers/ci-cd/builds/deploy-hooks/). * **vCPU**: The number of CPU cores available to your build. * **Memory**: The amount of memory available to your build. * **Disk space**: The amount of disk space available to your build. * **Environment variables**: The number of custom environment variables you can configure per Worker. * **Size per environment variable**: The maximum size for each individual environment variable. ```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/ci-cd/","name":"CI/CD"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/ci-cd/builds/","name":"Builds"}},{"@type":"ListItem","position":5,"item":{"@id":"/workers/ci-cd/builds/limits-and-pricing/","name":"Limits & pricing"}}]} ``` --- --- title: MCP server image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/ci-cd/builds/mcp-server.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # MCP server ```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/ci-cd/","name":"CI/CD"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/ci-cd/builds/","name":"Builds"}},{"@type":"ListItem","position":5,"item":{"@id":"/workers/ci-cd/builds/mcp-server/","name":"MCP server"}}]} ``` --- --- title: Troubleshooting builds description: Learn how to troubleshoot common and known issues in Workers Builds. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/ci-cd/builds/troubleshoot.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Troubleshooting builds This guide explains how to identify and resolve build errors, as well as troubleshoot common issues in the Workers Builds deployment process. To view your build history, go to your Worker project in the Cloudflare dashboard, select **Deployment**, select **View Build History** at the bottom of the page, and select the build you want to view. To retry a build, select the ellipses next to the build and select **Retry build**. Alternatively, you can select **Retry build** on the Build Details page. ## Known issues or limitations Here are some common build errors that may surface in the build logs or general issues and how you can resolve them. ### Workers name requirement `✘ [ERROR] The name in your Wrangler configuration file () must match the name of your Worker. Please update the name field in your Wrangler configuration file.` When connecting a Git repository to your Workers project, the specified name for the Worker on the Cloudflare dashboard must match the `name` argument in the Wrangler configuration file located in the specified root directory. If it does not match, update the name field in your Wrangler configuration file to match the name of the Worker on the dashboard. The build system uses the `name` argument in the Wrangler configuration file to determine which Worker to deploy to Cloudflare's global network. This requirement ensures consistency between the Worker's name on the dashboard and the deployed Worker. Note This does not apply to [Wrangler Environments](https://developers.cloudflare.com/workers/wrangler/environments/) if the Worker name before the `-` suffix matches the name in the Wrangler configuration file. For example, a Worker named `my-worker-staging` on the dashboard can be deployed from a repository that contains a Wrangler configuration file with the arguments `name = my-worker` and `[env.staging]` using the deploy command `npx wrangler deploy --env staging`. On Wrangler v3 and up, Workers Builds automatically matches the name of the connected Worker by overriding it with the `WRANGLER_CI_OVERRIDE_NAME` environment variable. ### Missing Wrangler configuration file `✘ [ERROR] Missing entry-point: The entry-point should be specified via the command line (e.g. wrangler deploy path/to/script) or the main config field.` If you see this error, a Wrangler configuration file is likely missing from the root directory. Navigate to **Settings** \> **Build** \> **Build Configuration** to update the root directory, or add a [Wrangler configuration file](https://developers.cloudflare.com/workers/wrangler/configuration/) to the specified directory. ### Incorrect account\_id `Could not route to /client/v4/accounts//workers/services/, perhaps your object identifier is invalid? [code: 7003]` If you see this error, the Wrangler configuration file likely has an `account_id` for a different account. Remove the `account_id` argument or update it with your account's `account_id`, available in **Workers & Pages Overview** under **Account Details**. ### Stale API token ` Failed: The build token selected for this build has been deleted or rolled and cannot be used for this build. Please update your build token in the Worker Builds settings and retry the build.` The API Token dropdown in Build Configuration settings may show stale tokens that were edited, deleted, or rolled. If you encounter an error due to a stale token, create a new API Token and select it for the build. ### Build timed out `Build was timed out` There is a maximum build duration of 20 minutes. If a build exceeds this time, then the build will be terminated and the above error log is shown. For more details, see [Workers Builds limits](https://developers.cloudflare.com/workers/ci-cd/builds/limits-and-pricing/). ### Git integration issues If you are running into errors associated with your Git integration, you can try removing access to your [GitHub](https://developers.cloudflare.com/workers/ci-cd/builds/git-integration/github-integration/#removing-access) or [GitLab](https://developers.cloudflare.com/workers/ci-cd/builds/git-integration/gitlab-integration/#removing-access) integration from Cloudflare, then reinstalling the [GitHub](https://developers.cloudflare.com/workers/ci-cd/builds/git-integration/github-integration/#reinstall-a-git-integration) or [GitLab](https://developers.cloudflare.com/workers/ci-cd/builds/git-integration/gitlab-integration/#reinstall-a-git-integration) integration. ## For additional support If you discover additional issues or would like to provide feedback, reach out to us in the [Cloudflare Developers Discord ↗](https://discord.com/channels/595317990191398933/1052656806058528849). ```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/ci-cd/","name":"CI/CD"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/ci-cd/builds/","name":"Builds"}},{"@type":"ListItem","position":5,"item":{"@id":"/workers/ci-cd/builds/troubleshoot/","name":"Troubleshooting builds"}}]} ``` --- --- title: External CI/CD description: Integrate Workers development into your existing continuous integration and continuous development workflows, such as GitHub Actions or GitLab Pipelines. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/ci-cd/external-cicd/index.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # External CI/CD Deploying Cloudflare Workers with CI/CD ensures reliable, automated deployments for every code change. If you prefer to use your existing CI/CD provider instead of [Workers Builds](https://developers.cloudflare.com/workers/ci-cd/builds/), this section offers guides for popular providers: * [**GitHub Actions**](https://developers.cloudflare.com/workers/ci-cd/external-cicd/github-actions/) * [**GitLab CI/CD**](https://developers.cloudflare.com/workers/ci-cd/external-cicd/gitlab-cicd/) Other CI/CD options including but not limited to Terraform, CircleCI, Jenkins, and more, can also be used to deploy Workers following a similar set up process. ```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/ci-cd/","name":"CI/CD"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/ci-cd/external-cicd/","name":"External CI/CD"}}]} ``` --- --- title: GitHub Actions description: Integrate Workers development into your existing GitHub Actions workflows. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/ci-cd/external-cicd/github-actions.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # GitHub Actions You can deploy Workers with [GitHub Actions ↗](https://github.com/marketplace/actions/deploy-to-cloudflare-workers-with-wrangler). Here is how you can set up your GitHub Actions workflow. ## 1\. Authentication When running Wrangler locally, authentication to the Cloudflare API happens via the [wrangler login](https://developers.cloudflare.com/workers/wrangler/commands/general/#login) command, which initiates an interactive authentication flow. Since CI/CD environments are non-interactive, Wrangler requires a [Cloudflare API token](https://developers.cloudflare.com/fundamentals/api/get-started/create-token/) and [account ID](https://developers.cloudflare.com/fundamentals/account/find-account-and-zone-ids/) to authenticate with the Cloudflare API. ### Cloudflare account ID To find your Cloudflare account ID, refer to [Find account and zone IDs](https://developers.cloudflare.com/fundamentals/account/find-account-and-zone-ids/). ### API token To create an API token to authenticate Wrangler in your CI job: 1. In the Cloudflare dashboard, go to the **Account API tokens** page. [ Go to **Account API tokens** ](https://dash.cloudflare.com/?to=/:account/api-tokens) 2. Select **Create Token** \> find **Edit Cloudflare Workers** \> select **Use Template**. 3. Customize your token name. 4. Scope your token. You will need to choose the account and zone resources that the generated API token will have access to. We recommend scoping these down as much as possible to limit the access of your token. For example, if you have access to three different Cloudflare accounts, you should restrict the generated API token to only the account on which you will be deploying a Worker. ## 2\. Set up CI/CD The method for running Wrangler in your CI/CD environment will depend on the specific setup for your project (whether you use GitHub Actions/Jenkins/GitLab or something else entirely). To set up your CI/CD: 1. Go to your CI/CD platform and add the following as secrets: * `CLOUDFLARE_ACCOUNT_ID`: Set to the [Cloudflare account ID](#cloudflare-account-id) for the account on which you want to deploy your Worker. * `CLOUDFLARE_API_TOKEN`: Set to the [Cloudflare API token you generated](#api-token). Warning Don't store the value of `CLOUDFLARE_API_TOKEN` in your repository, as it gives access to deploy Workers on your account. Instead, you should utilize your CI/CD provider's support for storing secrets. 1. Create a workflow that will be responsible for deploying the Worker. This workflow should run `wrangler deploy`. Review an example [GitHub Actions ↗](https://docs.github.com/en/actions/using-workflows/about-workflows) workflow in the follow section. ### GitHub Actions Cloudflare provides [an official action ↗](https://github.com/cloudflare/wrangler-action) for deploying Workers. Refer to the following example workflow which deploys your Worker on push to the `main` branch. YAML ``` name: Deploy Worker on: push: branches: - main jobs: deploy: runs-on: ubuntu-latest timeout-minutes: 60 steps: - uses: actions/checkout@v4 - name: Build & Deploy Worker uses: cloudflare/wrangler-action@v3 with: apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} ``` Explain Code ```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/ci-cd/","name":"CI/CD"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/ci-cd/external-cicd/","name":"External CI/CD"}},{"@type":"ListItem","position":5,"item":{"@id":"/workers/ci-cd/external-cicd/github-actions/","name":"GitHub Actions"}}]} ``` --- --- title: GitLab CI/CD description: Integrate Workers development into your existing GitLab Pipelines workflows. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/ci-cd/external-cicd/gitlab-cicd.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # GitLab CI/CD You can deploy Workers with [GitLab CI/CD ↗](https://docs.gitlab.com/ee/ci/pipelines/index.html). Here is how you can set up your Gitlab CI/CD pipeline. ## 1\. Authentication When running Wrangler locally, authentication to the Cloudflare API happens via the [wrangler login](https://developers.cloudflare.com/workers/wrangler/commands/general/#login) command, which initiates an interactive authentication flow. Since CI/CD environments are non-interactive, Wrangler requires a [Cloudflare API token](https://developers.cloudflare.com/fundamentals/api/get-started/create-token/) and [account ID](https://developers.cloudflare.com/fundamentals/account/find-account-and-zone-ids/) to authenticate with the Cloudflare API. ### Cloudflare account ID To find your Cloudflare account ID, refer to [Find account and zone IDs](https://developers.cloudflare.com/fundamentals/account/find-account-and-zone-ids/). ### API token To create an API token to authenticate Wrangler in your CI job: 1. In the Cloudflare dashboard, go to the **Account API tokens** page. [ Go to **Account API tokens** ](https://dash.cloudflare.com/?to=/:account/api-tokens) 2. Select **Create Token** \> find **Edit Cloudflare Workers** \> select **Use Template**. 3. Customize your token name. 4. Scope your token. You will need to choose the account and zone resources that the generated API token will have access to. We recommend scoping these down as much as possible to limit the access of your token. For example, if you have access to three different Cloudflare accounts, you should restrict the generated API token to only the account on which you will be deploying a Worker. ## 2\. Set up CI The method for running Wrangler in your CI/CD environment will depend on the specific setup for your project (whether you use GitHub Actions/Jenkins/GitLab or something else entirely). To set up your CI: 1. Go to your CI platform and add the following as secrets: * `CLOUDFLARE_ACCOUNT_ID`: Set to the [Cloudflare account ID](#cloudflare-account-id) for the account on which you want to deploy your Worker. * `CLOUDFLARE_API_TOKEN`: Set to the [Cloudflare API token you generated](#api-token). Warning Don't store the value of `CLOUDFLARE_API_TOKEN` in your repository, as it gives access to deploy Workers on your account. Instead, you should utilize your CI/CD provider's support for storing secrets. 1. Create a workflow that will be responsible for deploying the Worker. This workflow should run `wrangler deploy`. Review an example [GitHub Actions ↗](https://docs.github.com/en/actions/using-workflows/about-workflows) workflow in the following section. ### GitLab Pipelines Refer to [GitLab's blog ↗](https://about.gitlab.com/blog/2022/11/21/deploy-remix-with-gitlab-and-cloudflare/) for an example pipeline. Under the `script` key, replace `npm run deploy` with [npx wrangler deploy](https://developers.cloudflare.com/workers/wrangler/commands/general/#deploy). ```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/ci-cd/","name":"CI/CD"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/ci-cd/external-cicd/","name":"External CI/CD"}},{"@type":"ListItem","position":5,"item":{"@id":"/workers/ci-cd/external-cicd/gitlab-cicd/","name":"GitLab CI/CD"}}]} ``` --- --- title: Runtime APIs description: The Workers runtime is designed to be JavaScript standards compliant and web-interoperable. Wherever possible, it uses web platform APIs, so that code can be reused across client and server, as well as across WinterCG JavaScript runtimes. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/runtime-apis/index.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Runtime APIs The Workers runtime is designed to be [JavaScript standards compliant ↗](https://ecma-international.org/publications-and-standards/standards/ecma-262/) and web-interoperable. Wherever possible, it uses web platform APIs, so that code can be reused across client and server, as well as across [WinterCG ↗](https://wintercg.org/) JavaScript runtimes. [Workers runtime features](https://developers.cloudflare.com/workers/runtime-apis/) are [compatible with a subset of Node.js APIs](https://developers.cloudflare.com/workers/runtime-apis/nodejs) and the ability to set a [compatibility date or compatibility flag](https://developers.cloudflare.com/workers/configuration/compatibility-dates/). * [ Bindings (env) ](https://developers.cloudflare.com/workers/runtime-apis/bindings/) * [ Cache ](https://developers.cloudflare.com/workers/runtime-apis/cache/) * [ Console ](https://developers.cloudflare.com/workers/runtime-apis/console/) * [ Context (ctx) ](https://developers.cloudflare.com/workers/runtime-apis/context/) * [ Encoding ](https://developers.cloudflare.com/workers/runtime-apis/encoding/) * [ EventSource ](https://developers.cloudflare.com/workers/runtime-apis/eventsource/) * [ Fetch ](https://developers.cloudflare.com/workers/runtime-apis/fetch/) * [ Handlers ](https://developers.cloudflare.com/workers/runtime-apis/handlers/) * [ Headers ](https://developers.cloudflare.com/workers/runtime-apis/headers/) * [ HTMLRewriter ](https://developers.cloudflare.com/workers/runtime-apis/html-rewriter/) * [ MessageChannel ](https://developers.cloudflare.com/workers/runtime-apis/messagechannel/) * [ Node.js compatibility ](https://developers.cloudflare.com/workers/runtime-apis/nodejs/) * [ Performance and timers ](https://developers.cloudflare.com/workers/runtime-apis/performance/) * [ Remote-procedure call (RPC) ](https://developers.cloudflare.com/workers/runtime-apis/rpc/) * [ Request ](https://developers.cloudflare.com/workers/runtime-apis/request/) * [ Response ](https://developers.cloudflare.com/workers/runtime-apis/response/) * [ Scheduler ](https://developers.cloudflare.com/workers/runtime-apis/scheduler/) * [ Streams ](https://developers.cloudflare.com/workers/runtime-apis/streams/) * [ TCP sockets ](https://developers.cloudflare.com/workers/runtime-apis/tcp-sockets/) * [ Web Crypto ](https://developers.cloudflare.com/workers/runtime-apis/web-crypto/) * [ Web standards ](https://developers.cloudflare.com/workers/runtime-apis/web-standards/) * [ WebAssembly (Wasm) ](https://developers.cloudflare.com/workers/runtime-apis/webassembly/) * [ WebSockets ](https://developers.cloudflare.com/workers/runtime-apis/websockets/) ```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/runtime-apis/","name":"Runtime APIs"}}]} ``` --- --- title: Bindings (env) description: Worker Bindings that allow for interaction with other Cloudflare Resources. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) ### Tags [ Bindings ](https://developers.cloudflare.com/search/?tags=Bindings) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/runtime-apis/bindings/index.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Bindings (env) Bindings allow your Worker to interact with resources on the Cloudflare Developer Platform. Bindings provide better performance and less restrictions when accessing resources from Workers than the [REST APIs](https://developers.cloudflare.com/api/) which are intended for non-Workers applications. The following bindings are available today: * [ AI ](https://developers.cloudflare.com/workers-ai/get-started/workers-wrangler/#2-connect-your-worker-to-workers-ai) * [ Analytics Engine ](https://developers.cloudflare.com/analytics/analytics-engine) * [ Assets ](https://developers.cloudflare.com/workers/static-assets/binding/) * [ Browser Rendering ](https://developers.cloudflare.com/browser-rendering) * [ D1 ](https://developers.cloudflare.com/d1/worker-api/) * [ Dispatcher (Workers for Platforms) ](https://developers.cloudflare.com/cloudflare-for-platforms/workers-for-platforms/configuration/dynamic-dispatch/) * [ Durable Objects ](https://developers.cloudflare.com/durable-objects/api/) * [ Dynamic Worker Loaders ](https://developers.cloudflare.com/workers/runtime-apis/bindings/worker-loader/) * [ Environment Variables ](https://developers.cloudflare.com/workers/configuration/environment-variables/) * [ Hyperdrive ](https://developers.cloudflare.com/hyperdrive) * [ Images ](https://developers.cloudflare.com/images/transform-images/bindings/) * [ KV ](https://developers.cloudflare.com/kv/api/) * [ Media Transformations ](https://developers.cloudflare.com/stream/transform-videos/bindings/) * [ mTLS ](https://developers.cloudflare.com/workers/runtime-apis/bindings/mtls/) * [ Queues ](https://developers.cloudflare.com/queues/configuration/javascript-apis/) * [ R2 ](https://developers.cloudflare.com/r2/api/workers/workers-api-reference/) * [ Rate Limiting ](https://developers.cloudflare.com/workers/runtime-apis/bindings/rate-limit/) * [ Secrets ](https://developers.cloudflare.com/workers/configuration/secrets/) * [ Secrets Store ](https://developers.cloudflare.com/secrets-store/integrations/workers/) * [ Service bindings ](https://developers.cloudflare.com/workers/runtime-apis/bindings/service-bindings/) * [ Vectorize ](https://developers.cloudflare.com/vectorize/reference/client-api/) * [ Version metadata ](https://developers.cloudflare.com/workers/runtime-apis/bindings/version-metadata/) * [ Workflows ](https://developers.cloudflare.com/workflows/) ## What is a binding? When you declare a binding on your Worker, you grant it a specific capability, such as being able to read and write files to an [R2](https://developers.cloudflare.com/r2/) bucket. For example: * [ wrangler.jsonc ](#tab-panel-7696) * [ wrangler.toml ](#tab-panel-7697) JSONC ``` { "main": "./src/index.js", "r2_buckets": [ { "binding": "MY_BUCKET", "bucket_name": "" } ] } ``` TOML ``` main = "./src/index.js" [[r2_buckets]] binding = "MY_BUCKET" bucket_name = "" ``` * [ JavaScript ](#tab-panel-7682) * [ Python ](#tab-panel-7683) JavaScript ``` export default { async fetch(request, env) { const url = new URL(request.url); const key = url.pathname.slice(1); await env.MY_BUCKET.put(key, request.body); return new Response(`Put ${key} successfully!`); }, }; ``` Python ``` from workers import WorkerEntrypoint, Response from urllib.parse import urlparse class Default(WorkerEntrypoint): async def fetch(self, request): url = urlparse(request.url) key = url.path.slice(1) await self.env.MY_BUCKET.put(key, request.body) return Response(f"Put {key} successfully!") ``` You can think of a binding as a permission and an API in one piece. With bindings, you never have to add secret keys or tokens to your Worker in order to access resources on your Cloudflare account — the permission is embedded within the API itself. The underlying secret is never exposed to your Worker's code, and therefore can't be accidentally leaked. ## Making changes to bindings When you deploy a change to your Worker, and only change its bindings (i.e. you don't change the Worker's code), Cloudflare may reuse existing isolates that are already running your Worker. This improves performance — you can change an environment variable or other binding without unnecessarily reloading your code. As a result, you must be careful when "polluting" global scope with derivatives of your bindings. Anything you create there might continue to exist despite making changes to any underlying bindings. Consider an external client instance which uses a secret API key accessed from `env`: if you put this client instance in global scope and then make changes to the secret, a client instance using the original value might continue to exist. The correct approach would be to create a new client instance for each request. The following is a good approach: TypeScript ``` export default { fetch(request, env) { let client = new Client(env.MY_SECRET); // `client` is guaranteed to be up-to-date with the latest value of `env.MY_SECRET` since a new instance is constructed with every incoming request // ... do things with `client` }, }; ``` Compared to this alternative, which might have surprising and unwanted behavior: TypeScript ``` let client = undefined; export default { fetch(request, env) { client ??= new Client(env.MY_SECRET); // `client` here might not be updated when `env.MY_SECRET` changes, since it may already exist in global scope // ... do things with `client` }, }; ``` If you have more advanced needs, explore the [AsyncLocalStorage API](https://developers.cloudflare.com/workers/runtime-apis/nodejs/asynclocalstorage/), which provides a mechanism for exposing values down to child execution handlers. ## How to access `env` Bindings are located on the `env` object, which can be accessed in several ways: * It is an argument to entrypoint handlers such as [fetch](https://developers.cloudflare.com/workers/runtime-apis/fetch/): JavaScript ``` export default { async fetch(request, env) { return new Response(`Hi, ${env.NAME}`); }, }; ``` * It is as class property on [WorkerEntrypoint](https://developers.cloudflare.com/workers/runtime-apis/bindings/service-bindings/rpc/#bindings-env),[DurableObject](https://developers.cloudflare.com/durable-objects/), and [Workflow](https://developers.cloudflare.com/workflows/): * [ JavaScript ](#tab-panel-7684) * [ Python ](#tab-panel-7685) JavaScript ``` export class MyDurableObject extends DurableObject { async sayHello() { return `Hi, ${this.env.NAME}!`; } } ``` Python ``` from workers import WorkerEntrypoint, Response class Default(WorkerEntrypoint): async def fetch(self, request): return Response(f"Hi {self.env.NAME}") ``` * It can be imported from `cloudflare:workers`: * [ JavaScript ](#tab-panel-7686) * [ Python ](#tab-panel-7687) JavaScript ``` import { env } from "cloudflare:workers"; console.log(`Hi, ${env.Name}`); ``` Python ``` from workers import import_from_javascript env = import_from_javascript("cloudflare:workers").env print(f"Hi, {env.NAME}") ``` ### Importing `env` as a global Importing `env` from `cloudflare:workers` is useful when you need to access a binding such as [secrets](https://developers.cloudflare.com/workers/configuration/secrets/) or [environment variables](https://developers.cloudflare.com/workers/configuration/environment-variables/)in top-level global scope. For example, to initialize an API client: * [ JavaScript ](#tab-panel-7688) * [ Python ](#tab-panel-7689) JavaScript ``` import { env } from "cloudflare:workers"; import ApiClient from "example-api-client"; // API_KEY and LOG_LEVEL now usable in top-level scope let apiClient = ApiClient.new({ apiKey: env.API_KEY }); const LOG_LEVEL = env.LOG_LEVEL || "info"; export default { fetch(req) { // you can use apiClient or LOG_LEVEL, configured before any request is handled }, }; ``` Explain Code Python ``` from workers import WorkerEntrypoint, env from example_api_client import ApiClient api_client = ApiClient(api_key=env.API_KEY) LOG_LEVEL = getattr(env, "LOG_LEVEL", "info") class Default(WorkerEntrypoint): async def fetch(self, request): # ... ``` Workers do not allow I/O from outside a request context. This means that even though `env` is accessible from the top-level scope, you will not be able to access every binding's methods. For instance, environment variables and secrets are accessible, and you are able to call `env.NAMESPACE.get` to get a [Durable Object stub](https://developers.cloudflare.com/durable-objects/api/stub/) in the top-level context. However, calling methods on the Durable Object stub, making [calls to a KV store](https://developers.cloudflare.com/kv/api/), and [calling to other Workers](https://developers.cloudflare.com/workers/runtime-apis/bindings/service-bindings) will not work. * [ JavaScript ](#tab-panel-7690) * [ Python ](#tab-panel-7691) JavaScript ``` import { env } from "cloudflare:workers"; // This would error! // env.KV.get('my-key') export default { async fetch(req) { // This works let myVal = await env.KV.get("my-key"); Response.new(myVal); }, }; ``` Explain Code Python ``` from workers import Response, WorkerEntrypoint, env # This would fail! # env.KV.get('my-key') class Default(WorkerEntrypoint): async def fetch(self, request): # This works mv_val = await env.KV.get("my-key") return Response(my_val) ``` Explain Code Additionally, importing `env` from `cloudflare:workers` lets you avoid passing `env`as an argument through many function calls if you need to access a binding from a deeply-nested function. This can be helpful in a complex codebase. * [ JavaScript ](#tab-panel-7692) * [ Python ](#tab-panel-7693) JavaScript ``` import { env } from "cloudflare:workers"; export default { fetch(req) { Response.new(sayHello()); }, }; // env is not an argument to sayHello... function sayHello() { let myName = getName(); return `Hello, ${myName}`; } // ...nor is it an argument to getName function getName() { return env.MY_NAME; } ``` Explain Code Python ``` from workers import Response, WorkerEntrypoint, env class Default(WorkerEntrypoint): def fetch(req): return Response(say_hello()) # env is not an argument to say_hello... def say_hello(): my_name = get_name() return f"Hello, {myName}" # ...nor is it an argument to getName def get_name(): return env.MY_NAME ``` Explain Code Note While using `env` from `cloudflare:workers` may be simpler to write than passing it through a series of function calls, passing `env` as an argument is a helpful pattern for dependency injection and testing. ### Overriding `env` values The `withEnv` function provides a mechanism for overriding values of `env`. Imagine a user has defined the [environment variable](https://developers.cloudflare.com/workers/configuration/environment-variables/)"NAME" to be "Alice" in their Wrangler configuration file and deployed a Worker. By default, logging`env.NAME` would print "Alice". Using the `withEnv` function, you can override the value of "NAME". * [ JavaScript ](#tab-panel-7694) * [ Python ](#tab-panel-7695) JavaScript ``` import { env, withEnv } from "cloudflare:workers"; function logName() { console.log(env.NAME); } export default { fetch(req) { // this will log "Alice" logName(); withEnv({ NAME: "Bob" }, () => { // this will log "Bob" logName(); }); // ...etc... }, }; ``` Explain Code Python ``` from workers import Response, WorkerEntrypoint, env, patch_env def log_name(): print(env.NAME) class Default(WorkerEntrypoint): async def fetch(req): # this will log "Alice" log_name() with patch_env(NAME="Bob"): # this will log "Bob" log_name() # ...etc... ``` Explain Code This can be useful when testing code that relies on an imported `env` object. ```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/runtime-apis/","name":"Runtime APIs"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/runtime-apis/bindings/","name":"Bindings (env)"}}]} ``` --- --- title: AI description: Run generative AI inference and machine learning models on GPUs, without managing servers or infrastructure. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/runtime-apis/bindings/ai.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # AI ```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/runtime-apis/","name":"Runtime APIs"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/runtime-apis/bindings/","name":"Bindings (env)"}},{"@type":"ListItem","position":5,"item":{"@id":"/workers/runtime-apis/bindings/ai/","name":"AI"}}]} ``` --- --- title: Analytics Engine description: Write high-cardinality data and metrics at scale, directly from Workers. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/runtime-apis/bindings/analytics-engine.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Analytics Engine ```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/runtime-apis/","name":"Runtime APIs"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/runtime-apis/bindings/","name":"Bindings (env)"}},{"@type":"ListItem","position":5,"item":{"@id":"/workers/runtime-apis/bindings/analytics-engine/","name":"Analytics Engine"}}]} ``` --- --- title: Assets description: APIs available in Cloudflare Workers to interact with a collection of static assets. Static assets can be uploaded as part of your Worker. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/runtime-apis/bindings/assets.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Assets ```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/runtime-apis/","name":"Runtime APIs"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/runtime-apis/bindings/","name":"Bindings (env)"}},{"@type":"ListItem","position":5,"item":{"@id":"/workers/runtime-apis/bindings/assets/","name":"Assets"}}]} ``` --- --- title: Browser Rendering description: Programmatically control and interact with a headless browser instance. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/runtime-apis/bindings/browser-rendering.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Browser Rendering ```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/runtime-apis/","name":"Runtime APIs"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/runtime-apis/bindings/","name":"Bindings (env)"}},{"@type":"ListItem","position":5,"item":{"@id":"/workers/runtime-apis/bindings/browser-rendering/","name":"Browser Rendering"}}]} ``` --- --- title: D1 description: APIs available in Cloudflare Workers to interact with D1. D1 is Cloudflare's native serverless database. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/runtime-apis/bindings/D1.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # D1 ```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/runtime-apis/","name":"Runtime APIs"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/runtime-apis/bindings/","name":"Bindings (env)"}},{"@type":"ListItem","position":5,"item":{"@id":"/workers/runtime-apis/bindings/d1/","name":"D1"}}]} ``` --- --- title: Dispatcher (Workers for Platforms) description: Let your customers deploy their own code to your platform, and dynamically dispatch requests from your Worker to their Worker. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/runtime-apis/bindings/dispatcher.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Dispatcher (Workers for Platforms) ```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/runtime-apis/","name":"Runtime APIs"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/runtime-apis/bindings/","name":"Bindings (env)"}},{"@type":"ListItem","position":5,"item":{"@id":"/workers/runtime-apis/bindings/dispatcher/","name":"Dispatcher (Workers for Platforms)"}}]} ``` --- --- title: Durable Objects description: A globally distributed coordination API with strongly consistent storage. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/runtime-apis/bindings/durable-objects.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Durable Objects ```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/runtime-apis/","name":"Runtime APIs"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/runtime-apis/bindings/","name":"Bindings (env)"}},{"@type":"ListItem","position":5,"item":{"@id":"/workers/runtime-apis/bindings/durable-objects/","name":"Durable Objects"}}]} ``` --- --- title: Environment Variables description: Add string and JSON values to your Worker. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/runtime-apis/bindings/environment-variables.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Environment Variables ```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/runtime-apis/","name":"Runtime APIs"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/runtime-apis/bindings/","name":"Bindings (env)"}},{"@type":"ListItem","position":5,"item":{"@id":"/workers/runtime-apis/bindings/environment-variables/","name":"Environment Variables"}}]} ``` --- --- title: Hyperdrive description: Connect to your existing database from Workers, turning your existing regional database into a globally distributed database. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/runtime-apis/bindings/hyperdrive.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Hyperdrive ```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/runtime-apis/","name":"Runtime APIs"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/runtime-apis/bindings/","name":"Bindings (env)"}},{"@type":"ListItem","position":5,"item":{"@id":"/workers/runtime-apis/bindings/hyperdrive/","name":"Hyperdrive"}}]} ``` --- --- title: Images description: Store, transform, optimize, and deliver images at scale. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/runtime-apis/bindings/images.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Images ```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/runtime-apis/","name":"Runtime APIs"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/runtime-apis/bindings/","name":"Bindings (env)"}},{"@type":"ListItem","position":5,"item":{"@id":"/workers/runtime-apis/bindings/images/","name":"Images"}}]} ``` --- --- title: KV description: Global, low-latency, key-value data storage. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/runtime-apis/bindings/kv.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # KV ```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/runtime-apis/","name":"Runtime APIs"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/runtime-apis/bindings/","name":"Bindings (env)"}},{"@type":"ListItem","position":5,"item":{"@id":"/workers/runtime-apis/bindings/kv/","name":"KV"}}]} ``` --- --- title: Media Transformations description: Optimize, transform, and extract from short-form video. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/runtime-apis/bindings/media.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Media Transformations ```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/runtime-apis/","name":"Runtime APIs"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/runtime-apis/bindings/","name":"Bindings (env)"}},{"@type":"ListItem","position":5,"item":{"@id":"/workers/runtime-apis/bindings/media/","name":"Media Transformations"}}]} ``` --- --- title: mTLS description: Configure your Worker to present a client certificate to services that enforce an mTLS connection. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/runtime-apis/bindings/mTLS.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # mTLS When using [HTTPS ↗](https://www.cloudflare.com/learning/ssl/what-is-https/), a server presents a certificate for the client to authenticate in order to prove their identity. For even tighter security, some services require that the client also present a certificate. This process - known as [mTLS ↗](https://www.cloudflare.com/learning/access-management/what-is-mutual-tls/) \- moves authentication to the protocol of TLS, rather than managing it in application code. Connections from unauthorized clients are rejected during the TLS handshake instead. To present a client certificate when communicating with a service, create a mTLS certificate [binding](https://developers.cloudflare.com/workers/runtime-apis/bindings/) in your Worker project's Wrangler file. This will allow your Worker to present a client certificate to a service on your behalf. Warning Currently, mTLS for Workers cannot be used for requests made to a service that is a [proxied zone](https://developers.cloudflare.com/dns/proxy-status/) on Cloudflare. If your Worker presents a client certificate to a service proxied by Cloudflare, Cloudflare will return a `520` error. First, upload a certificate and its private key to your account using the [wrangler mtls-certificate](https://developers.cloudflare.com/workers/wrangler/commands/certificates/#mtls-certificate) command: Warning The `wrangler mtls-certificate upload` command requires the [SSL and Certificates Edit API token scope](https://developers.cloudflare.com/fundamentals/api/reference/permissions/). If you are using the OAuth flow triggered by `wrangler login`, the correct scope is set automatically. If you are using API tokens, refer to [Create an API token ↗](https://developers.cloudflare.com/fundamentals/api/get-started/create-token/) to set the right scope for your API token. Terminal window ``` npx wrangler mtls-certificate upload --cert cert.pem --key key.pem --name my-client-cert ``` Then, update your Worker project's Wrangler file to create an mTLS certificate binding: * [ wrangler.jsonc ](#tab-panel-7700) * [ wrangler.toml ](#tab-panel-7701) JSONC ``` { "mtls_certificates": [ { "binding": "MY_CERT", "certificate_id": "" } ] } ``` TOML ``` [[mtls_certificates]] binding = "MY_CERT" certificate_id = "" ``` Note Certificate IDs are displayed after uploading, and can also be viewed with the command `wrangler mtls-certificate list`. Adding an mTLS certificate binding includes a variable in the Worker's environment on which the `fetch()` method is available. This `fetch()` method uses the standard [Fetch](https://developers.cloudflare.com/workers/runtime-apis/fetch/) API and has the exact same signature as the global `fetch`, but always presents the client certificate when establishing the TLS connection. Note mTLS certificate bindings present an API similar to [service bindings](https://developers.cloudflare.com/workers/runtime-apis/bindings/service-bindings). ### Interface * [ JavaScript ](#tab-panel-7698) * [ TypeScript ](#tab-panel-7699) JavaScript ``` export default { async fetch(request, environment) { return await environment.MY_CERT.fetch("https://a-secured-origin.com"); }, }; ``` JavaScript ``` interface Env { MY_CERT: Fetcher; } export default { async fetch(request, environment): Promise { return await environment.MY_CERT.fetch("https://a-secured-origin.com") } } satisfies ExportedHandler; ``` ```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/runtime-apis/","name":"Runtime APIs"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/runtime-apis/bindings/","name":"Bindings (env)"}},{"@type":"ListItem","position":5,"item":{"@id":"/workers/runtime-apis/bindings/mtls/","name":"mTLS"}}]} ``` --- --- title: Queues description: Send and receive messages with guaranteed delivery. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/runtime-apis/bindings/queues.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Queues ```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/runtime-apis/","name":"Runtime APIs"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/runtime-apis/bindings/","name":"Bindings (env)"}},{"@type":"ListItem","position":5,"item":{"@id":"/workers/runtime-apis/bindings/queues/","name":"Queues"}}]} ``` --- --- title: R2 description: APIs available in Cloudflare Workers to read from and write to R2 buckets. R2 is S3-compatible, zero egress-fee, globally distributed object storage. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/runtime-apis/bindings/R2.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # R2 ```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/runtime-apis/","name":"Runtime APIs"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/runtime-apis/bindings/","name":"Bindings (env)"}},{"@type":"ListItem","position":5,"item":{"@id":"/workers/runtime-apis/bindings/r2/","name":"R2"}}]} ``` --- --- title: Rate Limiting description: Define rate limits and interact with them directly from your Cloudflare Worker image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/runtime-apis/bindings/rate-limit.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Rate Limiting The Rate Limiting API lets you define rate limits and write code around them in your Worker. You can use it to enforce: * Rate limits that are applied after your Worker starts, only once a specific part of your code is reached * Different rate limits for different types of customers or users (ex: free vs. paid) * Resource-specific or path-specific limits (ex: limit per API route) * Any combination of the above The Rate Limiting API is backed by the same infrastructure that serves [rate limiting rules](https://developers.cloudflare.com/waf/rate-limiting-rules/). Note You must use version 4.36.0 or later of the [Wrangler CLI](https://developers.cloudflare.com/workers/wrangler). ## Get started First, add a [binding](https://developers.cloudflare.com/workers/runtime-apis/bindings) to your Worker that gives it access to the Rate Limiting API: * [ wrangler.jsonc ](#tab-panel-7706) * [ wrangler.toml ](#tab-panel-7707) JSONC ``` { "main": "src/index.js", "ratelimits": [ { "name": "MY_RATE_LIMITER", // An identifier you define, that is unique to your Cloudflare account. // Must be an integer. "namespace_id": "1001", // Limit: the number of tokens allowed within a given period in a single // Cloudflare location // Period: the duration of the period, in seconds. Must be either 10 or 60 "simple": { "limit": 100, "period": 60 } } ] } ``` Explain Code TOML ``` main = "src/index.js" [[ratelimits]] name = "MY_RATE_LIMITER" namespace_id = "1001" [ratelimits.simple] limit = 100 period = 60 ``` This binding makes the `MY_RATE_LIMITER` binding available, which provides a `limit()` method: * [ JavaScript ](#tab-panel-7702) * [ TypeScript ](#tab-panel-7703) JavaScript ``` export default { async fetch(request, env) { const { pathname } = new URL(request.url) const { success } = await env.MY_RATE_LIMITER.limit({ key: pathname }) // key can be any string of your choosing if (!success) { return new Response(`429 Failure – rate limit exceeded for ${pathname}`, { status: 429 }) } return new Response(`Success!`) } } ``` Explain Code TypeScript ``` interface Env { MY_RATE_LIMITER: RateLimit; } export default { async fetch(request, env): Promise { const { pathname } = new URL(request.url) const { success } = await env.MY_RATE_LIMITER.limit({ key: pathname }) // key can be any string of your choosing if (!success) { return new Response(`429 Failure – rate limit exceeded for ${pathname}`, { status: 429 }) } return new Response(`Success!`) } } satisfies ExportedHandler; ``` Explain Code The `limit()` API accepts a single argument — a configuration object with the `key` field. * The key you provide can be any `string` value. * A common pattern is to define your key by combining a string that uniquely identifies the actor initiating the request (ex: a user ID or customer ID) and a string that identifies a specific resource (ex: a particular API route). You can define and configure multiple rate limiting configurations per Worker, which allows you to define different limits against incoming request and/or user parameters as needed to protect your application or upstream APIs. For example, here is how you can define two rate limiting configurations for free and paid tier users: * [ wrangler.jsonc ](#tab-panel-7708) * [ wrangler.toml ](#tab-panel-7709) JSONC ``` { "main": "src/index.js", "ratelimits": [ // Free user rate limiting { "name": "FREE_USER_RATE_LIMITER", "namespace_id": "1001", "simple": { "limit": 100, "period": 60 } }, // Paid user rate limiting { "name": "PAID_USER_RATE_LIMITER", "namespace_id": "1002", "simple": { "limit": 1000, "period": 60 } } ] } ``` Explain Code TOML ``` main = "src/index.js" [[ratelimits]] name = "FREE_USER_RATE_LIMITER" namespace_id = "1001" [ratelimits.simple] limit = 100 period = 60 [[ratelimits]] name = "PAID_USER_RATE_LIMITER" namespace_id = "1002" [ratelimits.simple] limit = 1_000 period = 60 ``` Explain Code ## Configuration A rate limiting binding has the following settings: | Setting | Type | Description | | ------------- | ------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | namespace\_id | string | A string containing a positive integer that uniquely defines this rate limiting namespace within your Cloudflare account (for example, "1001"). Although the value must be a valid integer, it is specified as a string. This is intentional. | | simple | object | The rate limit configuration. simple is the only supported type. | | simple.limit | number | The number of allowed requests (or calls to limit()) within the given period. | | simple.period | number | The duration of the rate limit window, in seconds. Must be either 10 or 60. | Note Two rate limiting bindings that share the same `namespace_id` — even across different Workers on the same account — share the same rate limit counters for a given key. This is intentional and allows you to enforce a single rate limit across multiple Workers. If you do not want to share rate limit state between bindings, use a unique `namespace_id` for each binding. For example, to apply a rate limit of 1500 requests per minute, you would define a rate limiting configuration as follows: * [ wrangler.jsonc ](#tab-panel-7704) * [ wrangler.toml ](#tab-panel-7705) JSONC ``` { "ratelimits": [ { "name": "MY_RATE_LIMITER", "namespace_id": "1001", // 1500 requests - calls to limit() increment this "simple": { "limit": 1500, "period": 60 } } ] } ``` Explain Code TOML ``` [[ratelimits]] name = "MY_RATE_LIMITER" namespace_id = "1001" [ratelimits.simple] limit = 1_500 period = 60 ``` ## Best practices The `key` passed to the `limit` function, that determines what to rate limit on, should represent a unique characteristic of a user or class of user that you wish to rate limit. * Good choices include API keys in `Authorization` HTTP headers, URL paths or routes, specific query parameters used by your application, and/or user IDs and tenant IDs. These are all stable identifiers and are unlikely to change from request-to-request. * It is not recommended to use IP addresses or locations (regions or countries), since these can be shared by many users in many valid cases. You may find yourself unintentionally rate limiting a wider group of users than you intended by rate limiting on these keys. TypeScript ``` // Recommended: use a key that represents a specific user or class of user const url = new URL(req.url) const userId = url.searchParams.get("userId") || "" const { success } = await env.MY_RATE_LIMITER.limit({ key: userId }) // Not recommended: many users may share a single IP, especially on mobile networks // or when using privacy-enabling proxies const ipAddress = req.headers.get("cf-connecting-ip") || "" const { success } = await env.MY_RATE_LIMITER.limit({ key: ipAddress }) ``` ## Locality Rate limits that you define and enforce in your Worker are local to the [Cloudflare location ↗](https://www.cloudflare.com/network/) that your Worker runs in. For example, if a request comes in from Sydney, Australia, to the Worker shown above, after 100 requests in a 60 second window, any further requests for a particular path would be rejected, and a 429 HTTP status code returned. But this would only apply to requests served in Sydney. For each unique key you pass to your rate limiting binding, there is a unique limit per Cloudflare location. ## Performance The Rate Limiting API in Workers is designed to be fast. The underlying counters are cached on the same machine that your Worker runs in, and updated asynchronously in the background by communicating with a backing store that is within the same Cloudflare location. This means that while in your code you `await` a call to the `limit()` method: JavaScript ``` const { success } = await env.MY_RATE_LIMITER.limit({ key: customerId }) ``` You are not waiting on a network request. You can use the Rate Limiting API without introducing any meaningful latency to your Worker. ## Accuracy The above also means that the Rate Limiting API is permissive, eventually consistent, and intentionally designed to not be used as an accurate accounting system. For example, if many requests come in to your Worker in a single Cloudflare location, all rate limited on the same key, the [isolate](https://developers.cloudflare.com/workers/reference/how-workers-works) that serves each request will check against its locally cached value of the rate limit. Very quickly, but not immediately, these requests will count towards the rate limit within that Cloudflare location. ## Monitoring Rate limiting bindings are not currently visible in the Cloudflare dashboard. To monitor rate-limited requests from your Worker: * **[Workers Observability](https://developers.cloudflare.com/workers/observability/)** — Use [Workers Logs](https://developers.cloudflare.com/workers/observability/logs/workers-logs/) and [Traces](https://developers.cloudflare.com/workers/observability/traces/) to observe HTTP 429 responses returned by your Worker when rate limits are exceeded. * **[Workers Analytics Engine](https://developers.cloudflare.com/analytics/analytics-engine/)** — Add an Analytics Engine binding to your Worker and emit custom data points (for example, a `rate_limited` event) when `limit()` returns `{ success: false }`. This lets you build dashboards and query rate limiting metrics over time. ## Examples * [@elithrar/workers-hono-rate-limit ↗](https://github.com/elithrar/workers-hono-rate-limit) — Middleware that lets you easily add rate limits to routes in your [Hono ↗](https://hono.dev/) application. * [@hono-rate-limiter/cloudflare ↗](https://github.com/rhinobase/hono-rate-limiter) — Middleware that lets you easily add rate limits to routes in your [Hono ↗](https://hono.dev/) application, with multiple data stores to choose from. * [hono-cf-rate-limit ↗](https://github.com/bytaesu/hono-cf-rate-limit) — Middleware for Hono applications that applies rate limiting in Cloudflare Workers, powered by Wrangler’s built-in features. ```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/runtime-apis/","name":"Runtime APIs"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/runtime-apis/bindings/","name":"Bindings (env)"}},{"@type":"ListItem","position":5,"item":{"@id":"/workers/runtime-apis/bindings/rate-limit/","name":"Rate Limiting"}}]} ``` --- --- title: Secrets description: Add encrypted secrets to your Worker. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/runtime-apis/bindings/secrets.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Secrets ```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/runtime-apis/","name":"Runtime APIs"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/runtime-apis/bindings/","name":"Bindings (env)"}},{"@type":"ListItem","position":5,"item":{"@id":"/workers/runtime-apis/bindings/secrets/","name":"Secrets"}}]} ``` --- --- title: Secrets Store description: Account-level secrets that can be added to Workers applications as a binding. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/runtime-apis/bindings/secrets-store.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Secrets Store ```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/runtime-apis/","name":"Runtime APIs"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/runtime-apis/bindings/","name":"Bindings (env)"}},{"@type":"ListItem","position":5,"item":{"@id":"/workers/runtime-apis/bindings/secrets-store/","name":"Secrets Store"}}]} ``` --- --- title: Service bindings description: Facilitate Worker-to-Worker communication. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) ### Tags [ Bindings ](https://developers.cloudflare.com/search/?tags=Bindings) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/runtime-apis/bindings/service-bindings/index.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Service bindings ## About Service bindings Service bindings allow one Worker to call into another, without going through a publicly-accessible URL. A Service binding allows Worker A to call a method on Worker B, or to forward a request from Worker A to Worker B. Service bindings provide the separation of concerns that microservice or service-oriented architectures provide, without configuration pain, performance overhead or need to learn RPC protocols. * **Service bindings are fast.** When you use Service Bindings, there is zero overhead or added latency. By default, both Workers run on the same thread of the same Cloudflare server. And when you enable [Smart Placement](https://developers.cloudflare.com/workers/configuration/placement/), each Worker runs in the optimal location for overall performance. * **Service bindings are not just HTTP.** Worker A can expose methods that can be directly called by Worker B. Communicating between services only requires writing JavaScript methods and classes. * **Service bindings don't increase costs.** You can split apart functionality into multiple Workers, without incurring additional costs. Learn more about [pricing for Service Bindings](https://developers.cloudflare.com/workers/platform/pricing/#service-bindings). ![Service bindings are a zero-cost abstraction](https://developers.cloudflare.com/_astro/service-bindings-comparison.CeB5uD1k_3yWPz.webp) Service bindings are commonly used to: * **Provide a shared internal service to multiple Workers.** For example, you can deploy an authentication service as its own Worker, and then have any number of separate Workers communicate with it via Service bindings. * **Isolate services from the public Internet.** You can deploy a Worker that is not reachable via the public Internet, and can only be reached via an explicit Service binding that another Worker declares. * **Allow teams to deploy code independently.** Team A can deploy their Worker on their own release schedule, and Team B can deploy their Worker separately. ## Configuration You add a Service binding by modifying the [Wrangler configuration file](https://developers.cloudflare.com/workers/wrangler/configuration/) of the caller — the Worker that you want to be able to initiate requests. For example, if you want Worker A to be able to call Worker B — you'd add the following to the [Wrangler configuration file](https://developers.cloudflare.com/workers/wrangler/configuration/) for Worker A: * [ wrangler.jsonc ](#tab-panel-7712) * [ wrangler.toml ](#tab-panel-7713) JSONC ``` { "services": [ { "binding": "", "service": "" } ] } ``` TOML ``` [[services]] binding = "" service = "" ``` * `binding`: The name of the key you want to expose on the `env` object. * `service`: The name of the target Worker you would like to communicate with. This Worker must be on your Cloudflare account. ## Interfaces Worker A that declares a Service binding to Worker B can call Worker B in two different ways: 1. [RPC](https://developers.cloudflare.com/workers/runtime-apis/bindings/service-bindings/rpc) lets you communicate between Workers using function calls that you define. For example, `await env.BINDING_NAME.myMethod(arg1)`. This is recommended for most use cases, and allows you to create your own internal APIs that your Worker makes available to other Workers. 2. [HTTP](https://developers.cloudflare.com/workers/runtime-apis/bindings/service-bindings/http) lets you communicate between Workers by calling the [fetch() handler](https://developers.cloudflare.com/workers/runtime-apis/handlers/fetch) from other Workers, sending `Request` objects and receiving `Response` objects back. For example, `env.BINDING_NAME.fetch(request)`. ## Example — build your first Service binding using RPC This example [extends the WorkerEntrypoint class](https://developers.cloudflare.com/workers/runtime-apis/bindings/service-bindings/rpc/#the-workerentrypoint-class) to support RPC-based Service bindings. First, create the Worker that you want to communicate with. Let's call this "Worker B". Worker B exposes the public method, `add(a, b)`: * [ wrangler.jsonc ](#tab-panel-7710) * [ wrangler.toml ](#tab-panel-7711) JSONC ``` { "$schema": "./node_modules/wrangler/config-schema.json", "name": "worker_b", "main": "./src/workerB.js" } ``` TOML ``` "$schema" = "./node_modules/wrangler/config-schema.json" name = "worker_b" main = "./src/workerB.js" ``` JavaScript ``` import { WorkerEntrypoint } from "cloudflare:workers"; export default class WorkerB extends WorkerEntrypoint { // Currently, entrypoints without a named handler are not supported async fetch() { return new Response(null, { status: 404 }); } async add(a, b) { return a + b; } } ``` Explain Code Next, create the Worker that will call Worker B. Let's call this "Worker A". Worker A declares a binding to Worker B. This is what gives it permission to call public methods on Worker B. * [ wrangler.jsonc ](#tab-panel-7714) * [ wrangler.toml ](#tab-panel-7715) JSONC ``` { "$schema": "./node_modules/wrangler/config-schema.json", "name": "worker_a", "main": "./src/workerA.js", "services": [ { "binding": "WORKER_B", "service": "worker_b" } ] } ``` Explain Code TOML ``` "$schema" = "./node_modules/wrangler/config-schema.json" name = "worker_a" main = "./src/workerA.js" [[services]] binding = "WORKER_B" service = "worker_b" ``` JavaScript ``` export default { async fetch(request, env) { const result = await env.WORKER_B.add(1, 2); return new Response(result); }, }; ``` To run both Worker A and Worker B in local development, you must run two instances of [Wrangler](https://developers.cloudflare.com/workers/wrangler) in your terminal. For each Worker, open a new terminal and run [npx wrangler@latest dev](https://developers.cloudflare.com/workers/wrangler/commands#dev). Each Worker is deployed separately. ## Lifecycle The Service bindings API is asynchronous — you must `await` any method you call. If Worker A invokes Worker B via a Service binding, and Worker A does not await the completion of Worker B, Worker B will be terminated early. For more about the lifecycle of calling a Worker over a Service Binding via RPC, refer to the [RPC Lifecycle](https://developers.cloudflare.com/workers/runtime-apis/rpc/lifecycle) docs. ## Local development Local development is supported for Service bindings. For each Worker, open a new terminal and use [wrangler dev](https://developers.cloudflare.com/workers/wrangler/commands/general/#dev) in the relevant directory. When running `wrangler dev`, service bindings will show as `connected`/`not connected` depending on whether Wrangler can find a running `wrangler dev` session for that Worker. For example: Terminal window ``` $ wrangler dev ... Your worker has access to the following bindings: - Services: - SOME_OTHER_WORKER: some-other-worker [connected] - ANOTHER_WORKER: another-worker [not connected] ``` Wrangler also supports running multiple Workers at once with one command. To try it out, pass multiple `-c` flags to Wrangler, like this: `wrangler dev -c wrangler.json -c ../other-worker/wrangler.json`. The first config will be treated as the _primary_ worker, which will be exposed over HTTP as usual at `http://localhost:8787`. The remaining config files will be treated as _secondary_ and will only be accessible via a service binding from the primary worker. Warning Support for running multiple Workers at once with one Wrangler command is experimental, and subject to change as we work on the experience. If you run into bugs or have any feedback, [open an issue on the workers-sdk repository ↗](https://github.com/cloudflare/workers-sdk/issues/new) ## Deployment Workers using Service bindings are deployed separately. When getting started and deploying for the first time, this means that the target Worker (Worker B in the examples above) must be deployed first, before Worker A. Otherwise, when you attempt to deploy Worker A, deployment will fail, because Worker A declares a binding to Worker B, which does not yet exist. When making changes to existing Workers, in most cases you should: * Deploy changes to Worker B first, in a way that is compatible with the existing Worker A. For example, add a new method to Worker B. * Next, deploy changes to Worker A. For example, call the new method on Worker B, from Worker A. * Finally, remove any unused code. For example, delete the previously used method on Worker B. ## Smart Placement [Smart Placement](https://developers.cloudflare.com/workers/configuration/placement/) automatically places your Worker in an optimal location that minimizes latency. You can use Smart Placement together with Service bindings to split your Worker into two services: ![Smart Placement and Service Bindings](https://developers.cloudflare.com/_astro/smart-placement-service-bindings.Ce58BYeF_ZmD4l8.webp) Refer to the [docs on Smart Placement](https://developers.cloudflare.com/workers/configuration/placement/#multiple-workers) for more. ## Limits Service bindings have the following limits: * Each request to a Worker via a Service binding counts toward your [subrequest limit](https://developers.cloudflare.com/workers/platform/limits/#subrequests). * A single request has a maximum of 32 Worker invocations, and each call to a Service binding counts towards this limit. Subsequent calls will throw an exception. * Calling a service binding does not count towards [simultaneous open connection limits](https://developers.cloudflare.com/workers/platform/limits/#simultaneous-open-connections) ```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/runtime-apis/","name":"Runtime APIs"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/runtime-apis/bindings/","name":"Bindings (env)"}},{"@type":"ListItem","position":5,"item":{"@id":"/workers/runtime-apis/bindings/service-bindings/","name":"Service bindings"}}]} ``` --- --- title: HTTP description: Facilitate Worker-to-Worker communication by forwarding Request objects. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/runtime-apis/bindings/service-bindings/http.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # HTTP Worker A that declares a Service binding to Worker B can forward a [Request](https://developers.cloudflare.com/workers/runtime-apis/request/) object to Worker B, by calling the `fetch()` method that is exposed on the binding object. For example, consider the following Worker that implements a [fetch() handler](https://developers.cloudflare.com/workers/runtime-apis/handlers/fetch/): * [ wrangler.jsonc ](#tab-panel-7716) * [ wrangler.toml ](#tab-panel-7717) JSONC ``` { "$schema": "./node_modules/wrangler/config-schema.json", "name": "worker_b", "main": "./src/workerB.js" } ``` TOML ``` "$schema" = "./node_modules/wrangler/config-schema.json" name = "worker_b" main = "./src/workerB.js" ``` JavaScript ``` export default { async fetch(request, env, ctx) { return new Response("Hello World!"); } } ``` The following Worker declares a binding to the Worker above: * [ wrangler.jsonc ](#tab-panel-7718) * [ wrangler.toml ](#tab-panel-7719) JSONC ``` { "$schema": "./node_modules/wrangler/config-schema.json", "name": "worker_a", "main": "./src/workerA.js", "services": [ { "binding": "WORKER_B", "service": "worker_b" } ] } ``` Explain Code TOML ``` "$schema" = "./node_modules/wrangler/config-schema.json" name = "worker_a" main = "./src/workerA.js" [[services]] binding = "WORKER_B" service = "worker_b" ``` And then can forward a request to it: JavaScript ``` export default { async fetch(request, env) { return await env.WORKER_B.fetch(request); }, }; ``` Note If you construct a new request manually, rather than forwarding an existing one, ensure that you provide a valid and fully-qualified URL with a hostname. For example: JavaScript ``` export default { async fetch(request, env) { // provide a valid URL let newRequest = new Request("https://valid-url.com", { method: "GET" }); let response = await env.WORKER_B.fetch(newRequest); return response; } }; ``` ```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/runtime-apis/","name":"Runtime APIs"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/runtime-apis/bindings/","name":"Bindings (env)"}},{"@type":"ListItem","position":5,"item":{"@id":"/workers/runtime-apis/bindings/service-bindings/","name":"Service bindings"}},{"@type":"ListItem","position":6,"item":{"@id":"/workers/runtime-apis/bindings/service-bindings/http/","name":"HTTP"}}]} ``` --- --- title: RPC (WorkerEntrypoint) description: Facilitate Worker-to-Worker communication via RPC. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) ### Tags [ RPC ](https://developers.cloudflare.com/search/?tags=RPC) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/runtime-apis/bindings/service-bindings/rpc.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # RPC (WorkerEntrypoint) [Service bindings](https://developers.cloudflare.com/workers/runtime-apis/bindings/service-bindings) allow one Worker to call into another, without going through a publicly-accessible URL. You can use Service bindings to create your own internal APIs that your Worker makes available to other Workers. This can be done by extending the built-in `WorkerEntrypoint` class, and adding your own public methods. These public methods can then be directly called by other Workers on your Cloudflare account that declare a [binding](https://developers.cloudflare.com/workers/runtime-apis/bindings) to this Worker. The [RPC system in Workers](https://developers.cloudflare.com/workers/runtime-apis/rpc) is designed feel as similar as possible to calling a JavaScript function in the same Worker. In most cases, you should be able to write code in the same way you would if everything was in a single Worker. Note You can also use RPC to communicate between Workers and [Durable Objects](https://developers.cloudflare.com/durable-objects/best-practices/create-durable-object-stubs-and-send-requests/#invoke-rpc-methods). ## Example For example, the following Worker implements the public method `add(a, b)`: For example, if Worker B implements the public method `add(a, b)`: * [ wrangler.jsonc ](#tab-panel-7730) * [ wrangler.toml ](#tab-panel-7731) JSONC ``` { "$schema": "./node_modules/wrangler/config-schema.json", "name": "worker_b", "main": "./src/workerB.js" } ``` TOML ``` "$schema" = "./node_modules/wrangler/config-schema.json" name = "worker_b" main = "./src/workerB.js" ``` * [ JavaScript ](#tab-panel-7737) * [ TypeScript ](#tab-panel-7738) * [ Python ](#tab-panel-7739) JavaScript ``` import { WorkerEntrypoint } from "cloudflare:workers"; export default class extends WorkerEntrypoint { async fetch() { return new Response("Hello from Worker B"); } add(a, b) { return a + b; } } ``` Explain Code TypeScript ``` import { WorkerEntrypoint } from "cloudflare:workers"; export default class extends WorkerEntrypoint { async fetch() { return new Response("Hello from Worker B"); } add(a: number, b: number) { return a + b; } } ``` Explain Code Python ``` from workers import WorkerEntrypoint, Response class Default(WorkerEntrypoint): async def fetch(self, request): return Response("Hello from Worker B") def add(self, a: int, b: int) -> int: return a + b ``` Worker A can declare a [binding](https://developers.cloudflare.com/workers/runtime-apis/bindings) to Worker B: * [ wrangler.jsonc ](#tab-panel-7732) * [ wrangler.toml ](#tab-panel-7733) JSONC ``` { "$schema": "./node_modules/wrangler/config-schema.json", "name": "worker_a", "main": "./src/workerA.js", "services": [ { "binding": "WORKER_B", "service": "worker_b" } ] } ``` Explain Code TOML ``` "$schema" = "./node_modules/wrangler/config-schema.json" name = "worker_a" main = "./src/workerA.js" [[services]] binding = "WORKER_B" service = "worker_b" ``` Making it possible for Worker A to call the `add()` method from Worker B: * [ JavaScript ](#tab-panel-7734) * [ TypeScript ](#tab-panel-7735) * [ Python ](#tab-panel-7736) JavaScript ``` export default { async fetch(request, env) { const result = await env.WORKER_B.add(1, 2); return new Response(result); }, }; ``` TypeScript ``` export default { async fetch(request, env) { const result = await env.WORKER_B.add(1, 2); return new Response(result); }, }; ``` Python ``` from workers import WorkerEntrypoint, Response class Default(WorkerEntrypoint): async def fetch(self, request): result = await self.env.WORKER_B.add(1, 2) return Response(f"Result: {result}") ``` You do not need to learn, implement, or think about special protocols to use the RPC system. The client, in this case Worker A, calls Worker B and tells it to execute a specific procedure using specific arguments that the client provides. This is accomplished with standard JavaScript classes. ## The `WorkerEntrypoint` Class To provide RPC methods from your Worker, you must extend the `WorkerEntrypoint` class, as shown in the example below: JavaScript ``` import { WorkerEntrypoint } from "cloudflare:workers"; export default class extends WorkerEntrypoint { async add(a, b) { return a + b; } } ``` A new instance of the class is created every time the Worker is called. Note that even though the Worker is implemented as a class, it is still stateless — the class instance only lasts for the duration of the invocation. If you need to persist or coordinate state in Workers, you should use [Durable Objects](https://developers.cloudflare.com/durable-objects). ### Bindings (`env`) The [env](https://developers.cloudflare.com/workers/runtime-apis/bindings) object is exposed as a class property of the `WorkerEntrypoint` class. For example, a Worker that declares a binding to the [environment variable](https://developers.cloudflare.com/workers/configuration/environment-variables/) `GREETING`: * [ wrangler.jsonc ](#tab-panel-7720) * [ wrangler.toml ](#tab-panel-7721) JSONC ``` { "$schema": "./node_modules/wrangler/config-schema.json", "name": "my-worker", "vars": { "GREETING": "Hello" } } ``` TOML ``` "$schema" = "./node_modules/wrangler/config-schema.json" name = "my-worker" [vars] GREETING = "Hello" ``` Can access it by calling `this.env.GREETING`: JavaScript ``` import { WorkerEntrypoint } from "cloudflare:workers"; export default class extends WorkerEntrypoint { fetch() { return new Response("Hello from my-worker"); } async greet(name) { return this.env.GREETING + name; } } ``` You can use any type of [binding](https://developers.cloudflare.com/workers/runtime-apis/bindings) this way. ### Lifecycle methods (`ctx`) The [ctx](https://developers.cloudflare.com/workers/runtime-apis/context) object is exposed as a class property of the `WorkerEntrypoint` class. For example, you can extend the lifetime of the invocation context by calling the `waitUntil()` method: JavaScript ``` import { WorkerEntrypoint } from "cloudflare:workers"; export default class extends WorkerEntrypoint { fetch() { return new Response("Hello from my-worker"); } async signup(email, name) { // sendEvent() will continue running, even after this method returns a value to the caller this.ctx.waitUntil(this.#sendEvent("signup", email)) // Perform any other work return "Success"; } async #sendEvent(eventName, email) { //... } } ``` Explain Code ### Fetching static assets If your Worker has a [static assets binding](https://developers.cloudflare.com/workers/static-assets/binding/), you can call `this.env.ASSETS.fetch()` from within an RPC method. Since RPC methods do not receive a `request` parameter, construct a `Request` or URL with any hostname — the hostname is ignored by the assets binding, only the pathname matters: * [ JavaScript ](#tab-panel-7728) * [ TypeScript ](#tab-panel-7729) JavaScript ``` import { WorkerEntrypoint } from "cloudflare:workers"; export class ImageWorker extends WorkerEntrypoint { async getImage(path) { return this.env.ASSETS.fetch(new Request(`https://assets.local${path}`)); } } ``` TypeScript ``` import { WorkerEntrypoint } from "cloudflare:workers"; export class ImageWorker extends WorkerEntrypoint { async getImage(path: string): Promise { return this.env.ASSETS.fetch( new Request(`https://assets.local${path}`) ); } } ``` The caller can then invoke this method via RPC: * [ JavaScript ](#tab-panel-7724) * [ TypeScript ](#tab-panel-7725) JavaScript ``` const response = await env.IMAGE_SERVICE.getImage("/images/logo.png"); ``` TypeScript ``` const response = await env.IMAGE_SERVICE.getImage("/images/logo.png"); ``` Note When fetching assets via the binding, the hostname (for example, `assets.local`) is not meaningful — any valid hostname will work. Only the URL pathname is used to match assets. The convention `assets.local` is used for clarity. ## Named entrypoints You can also export any number of named `WorkerEntrypoint` classes from within a single Worker, in addition to the default export. You can then declare a Service binding to a specific named entrypoint. You can use this to group multiple pieces of compute together. For example, you might create a distinct `WorkerEntrypoint` for each permission role in your application, and use these to provide role-specific RPC methods: * [ wrangler.jsonc ](#tab-panel-7722) * [ wrangler.toml ](#tab-panel-7723) JSONC ``` { "$schema": "./node_modules/wrangler/config-schema.json", "name": "todo-app", "d1_databases": [ { "binding": "D1", "database_name": "todo-app-db", "database_id": "" } ] } ``` Explain Code TOML ``` "$schema" = "./node_modules/wrangler/config-schema.json" name = "todo-app" [[d1_databases]] binding = "D1" database_name = "todo-app-db" database_id = "" ``` JavaScript ``` import { WorkerEntrypoint } from "cloudflare:workers"; export class AdminEntrypoint extends WorkerEntrypoint { async createUser(username) { await this.env.D1.prepare("INSERT INTO users (username) VALUES (?)") .bind(username) .run(); } async deleteUser(username) { await this.env.D1.prepare("DELETE FROM users WHERE username = ?") .bind(username) .run(); } } export class UserEntrypoint extends WorkerEntrypoint { async getTasks(userId) { return await this.env.D1.prepare( "SELECT title FROM tasks WHERE user_id = ?" ) .bind(userId) .run(); } async createTask(userId, title) { await this.env.D1.prepare( "INSERT INTO tasks (user_id, title) VALUES (?, ?)" ) .bind(userId, title) .run(); } } export default class extends WorkerEntrypoint { async fetch(request, env) { return new Response("Hello from my to do app"); } } ``` Explain Code You can then declare a Service binding directly to `AdminEntrypoint` in another Worker: * [ wrangler.jsonc ](#tab-panel-7726) * [ wrangler.toml ](#tab-panel-7727) JSONC ``` { "$schema": "./node_modules/wrangler/config-schema.json", "name": "admin-app", "services": [ { "binding": "ADMIN", "service": "todo-app", "entrypoint": "AdminEntrypoint" } ] } ``` Explain Code TOML ``` "$schema" = "./node_modules/wrangler/config-schema.json" name = "admin-app" [[services]] binding = "ADMIN" service = "todo-app" entrypoint = "AdminEntrypoint" ``` JavaScript ``` export default { async fetch(request, env) { await env.ADMIN.createUser("aNewUser"); return new Response("Hello from admin app"); }, }; ``` You can learn more about how to configure D1 in the [D1 documentation](https://developers.cloudflare.com/d1/get-started/#3-bind-your-worker-to-your-d1-database). You can try out a complete example of this to do app, as well as a Discord bot built with named entrypoints, by cloning the [cloudflare/js-rpc-and-entrypoints-demo repository ↗](https://github.com/cloudflare/js-rpc-and-entrypoints-demo) from GitHub. ## Further reading * [ Lifecycle ](https://developers.cloudflare.com/workers/runtime-apis/rpc/lifecycle/) * [ Reserved Methods ](https://developers.cloudflare.com/workers/runtime-apis/rpc/reserved-methods/) * [ Visibility and Security Model ](https://developers.cloudflare.com/workers/runtime-apis/rpc/visibility/) * [ TypeScript ](https://developers.cloudflare.com/workers/runtime-apis/rpc/typescript/) * [ Error handling ](https://developers.cloudflare.com/workers/runtime-apis/rpc/error-handling/) ```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/runtime-apis/","name":"Runtime APIs"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/runtime-apis/bindings/","name":"Bindings (env)"}},{"@type":"ListItem","position":5,"item":{"@id":"/workers/runtime-apis/bindings/service-bindings/","name":"Service bindings"}},{"@type":"ListItem","position":6,"item":{"@id":"/workers/runtime-apis/bindings/service-bindings/rpc/","name":"RPC (WorkerEntrypoint)"}}]} ``` --- --- title: Vectorize description: APIs available in Cloudflare Workers to interact with Vectorize. Vectorize is Cloudflare's globally distributed vector database. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/runtime-apis/bindings/vectorize.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Vectorize ```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/runtime-apis/","name":"Runtime APIs"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/runtime-apis/bindings/","name":"Bindings (env)"}},{"@type":"ListItem","position":5,"item":{"@id":"/workers/runtime-apis/bindings/vectorize/","name":"Vectorize"}}]} ``` --- --- title: Version metadata description: Exposes Worker version metadata (`versionID` and `versionTag`). These fields can be added to events emitted from the Worker to send to downstream observability systems. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/runtime-apis/bindings/version-metadata.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Version metadata The version metadata binding can be used to access metadata associated with a [version](https://developers.cloudflare.com/workers/configuration/versions-and-deployments/#versions) from inside the Workers runtime. Worker version ID, version tag and timestamp of when the version was created are available through the version metadata binding. They can be used in events sent to [Workers Analytics Engine](https://developers.cloudflare.com/analytics/analytics-engine/) or to any third-party analytics/metrics service in order to aggregate by Worker version. To use the version metadata binding, update your Worker's Wrangler file: * [ wrangler.jsonc ](#tab-panel-7742) * [ wrangler.toml ](#tab-panel-7743) JSONC ``` { "version_metadata": { "binding": "CF_VERSION_METADATA" } } ``` TOML ``` [version_metadata] binding = "CF_VERSION_METADATA" ``` ### Interface An example of how to access the version ID and version tag from within a Worker to send events to [Workers Analytics Engine](https://developers.cloudflare.com/analytics/analytics-engine/): * [ JavaScript ](#tab-panel-7740) * [ TypeScript ](#tab-panel-7741) JavaScript ``` export default { async fetch(request, env, ctx) { const { id: versionId, tag: versionTag, timestamp: versionTimestamp } = env.CF_VERSION_METADATA; env.WAE.writeDataPoint({ indexes: [versionId], blobs: [versionTag, versionTimestamp], //... }); //... }, }; ``` Explain Code TypeScript ``` interface Environment { CF_VERSION_METADATA: WorkerVersionMetadata; WAE: AnalyticsEngineDataset; } export default { async fetch(request, env, ctx) { const { id: versionId, tag: versionTag } = env.CF_VERSION_METADATA; env.WAE.writeDataPoint({ indexes: [versionId], blobs: [versionTag], //... }); //... }, } satisfies ExportedHandler; ``` Explain Code ```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/runtime-apis/","name":"Runtime APIs"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/runtime-apis/bindings/","name":"Bindings (env)"}},{"@type":"ListItem","position":5,"item":{"@id":"/workers/runtime-apis/bindings/version-metadata/","name":"Version metadata"}}]} ``` --- --- title: Dynamic Workers description: Spin up isolated Workers on demand to execute code. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/dynamic-workers/index.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Dynamic Workers Spin up Workers at runtime to execute code on-demand in a secure, sandboxed environment. Dynamic Workers let you spin up an unlimited number of Workers to execute arbitrary code specified at runtime. Dynamic Workers can be used as a lightweight alternative to containers for securely sandboxing code you don't trust. Dynamic Workers are the lowest-level primitive for spinning up a Worker, giving you full control over defining how the Worker is composed, which bindings it receives, whether it can reach the network, and more. ### Get started Deploy the [Dynamic Workers Playground ↗](https://github.com/cloudflare/agents/tree/main/examples/dynamic-workers-playground) to create and run Workers dynamically from code you write or import from GitHub, with real-time logs and observability. [![Deploy to Cloudflare](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/dinasaur404/dynamic-workers-playground) ## Use Dynamic Workers for Use this pattern when code needs to run quickly in a secure, isolated environment. * **AI Agent "Code Mode"**: LLMs are trained to write code. Instead of supplying an agent with tool calls to perform tasks, give it an API and let it write and execute code. Save up to 80% in inference tokens and cost by allowing the agent to programmatically process data instead of sending it all through the LLM. * **AI-generated applications / "Vibe Code"**: Run generated code for prototypes, projects, and automations in a secure, isolated sandboxed environment. * **Fast development and previews**: Load prototypes, previews, and playgrounds in milliseconds. * **Custom automations**: Create custom tools on the fly that execute a task, call an integration, or automate a workflow. * **Platforms**: Run applications uploaded by your users. ## Features Because you compose the Worker that runs the code at runtime, you control how that Worker is configured and what it can access. * **[Bindings](https://developers.cloudflare.com/dynamic-workers/usage/bindings/)**: Decide which bindings and structured data the dynamic Worker receives. * **[Observability](https://developers.cloudflare.com/dynamic-workers/usage/observability/)**: Attach Tail Workers and capture logs for each run. * **[Network access](https://developers.cloudflare.com/dynamic-workers/usage/egress-control/)**: Intercept or block Internet access for outbound requests. * **[Durable Object Facets](https://developers.cloudflare.com/dynamic-workers/usage/durable-object-facets/)**: Run dynamically-loaded code as a Durable Object with its own isolated SQLite storage. ```json {"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/dynamic-workers/","name":"Dynamic Workers"}}]} ``` --- --- title: Workflows description: APIs available in Cloudflare Workers to interact with Workflows. Workflows allow you to build durable, multi-step applications using Workers. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/runtime-apis/bindings/workflows.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Workflows ```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/runtime-apis/","name":"Runtime APIs"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/runtime-apis/bindings/","name":"Bindings (env)"}},{"@type":"ListItem","position":5,"item":{"@id":"/workers/runtime-apis/bindings/workflows/","name":"Workflows"}}]} ``` --- --- title: Cache description: Control reading and writing from the Cloudflare global network cache. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/runtime-apis/cache.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Cache ## Background The [Cache API ↗](https://developer.mozilla.org/en-US/docs/Web/API/Cache) allows fine grained control of reading and writing from the [Cloudflare global network ↗](https://www.cloudflare.com/network/) cache. The Cache API is available globally but the contents of the cache do not replicate outside of the originating data center. A `GET /users` response can be cached in the originating data center, but will not exist in another data center unless it has been explicitly created. Tiered caching The `cache.put` method is not compatible with tiered caching. Refer to [Cache API](https://developers.cloudflare.com/workers/reference/how-the-cache-works/#cache-api) for more information. To perform tiered caching, use the [fetch API](https://developers.cloudflare.com/workers/reference/how-the-cache-works/#interact-with-the-cloudflare-cache). Workers deployed to custom domains have access to functional `cache` operations. So do [Pages functions](https://developers.cloudflare.com/pages/functions/), whether attached to custom domains or `*.pages.dev` domains. However, any Cache API operations in the Cloudflare Workers dashboard editor and [Playground](https://developers.cloudflare.com/workers/playground/) previews will have no impact. For Workers fronted by [Cloudflare Access ↗](https://www.cloudflare.com/teams/access/), the Cache API is not currently available. Note This individualized zone cache object differs from Cloudflare’s Global CDN. For details, refer to [How the cache works](https://developers.cloudflare.com/workers/reference/how-the-cache-works/). --- ## Accessing Cache The `caches.default` API is strongly influenced by the web browsers’ Cache API, but there are some important differences. For instance, Cloudflare Workers runtime exposes a single global cache object. JavaScript ``` let cache = caches.default; await cache.match(request); ``` You may create and manage additional Cache instances via the [caches.open ↗](https://developer.mozilla.org/en-US/docs/Web/API/CacheStorage/open) method. JavaScript ``` let myCache = await caches.open('custom:cache'); await myCache.match(request); ``` Note When using the cache API, avoid overriding the hostname in cache requests, as this can lead to unnecessary DNS lookups and cache inefficiencies. Always use the hostname that matches the domain associated with your Worker. JavaScript ``` // recommended approach: use your Worker hostname to ensure efficient caching request.url = "https://your-Worker-hostname.com/"; let myCache = await caches.open('custom:cache'); let response = await myCache.match(request); ``` --- ## Headers Our implementation of the Cache API respects the following HTTP headers on the response passed to `put()`: * `Cache-Control` * Controls caching directives. This is consistent with [Cloudflare Cache-Control Directives](https://developers.cloudflare.com/cache/concepts/cache-control#cache-control-directives). Refer to [Edge TTL](https://developers.cloudflare.com/cache/how-to/configure-cache-status-code#edge-ttl) for a list of HTTP response codes and their TTL when `Cache-Control` directives are not present. * `Cache-Tag` * Allows resource purging by tag(s) later. * `ETag` * Allows `cache.match()` to evaluate conditional requests with `If-None-Match`. * `Expires` string * A string that specifies when the resource becomes invalid. * `Last-Modified` * Allows `cache.match()` to evaluate conditional requests with `If-Modified-Since`. This differs from the web browser Cache API as they do not honor any headers on the request or response. Note Responses with `Set-Cookie` headers are never cached, because this sometimes indicates that the response contains unique data. To store a response with a `Set-Cookie` header, either delete that header or set `Cache-Control: private=Set-Cookie` on the response before calling `cache.put()`. Use the `Cache-Control` method to store the response without the `Set-Cookie` header. --- ## Methods ### `Put` JavaScript ``` cache.put(request, response); ``` * `put(request, response)` : Promise * Attempts to add a response to the cache, using the given request as the key. Returns a promise that resolves to `undefined` regardless of whether the cache successfully stored the response. Note The `stale-while-revalidate` and `stale-if-error` directives are not supported when using the `cache.put` or `cache.match` methods. #### Parameters * `request` string | Request * Either a string or a [Request](https://developers.cloudflare.com/workers/runtime-apis/request/) object to serve as the key. If a string is passed, it is interpreted as the URL for a new Request object. * `response` Response * A [Response](https://developers.cloudflare.com/workers/runtime-apis/response/) object to store under the given key. #### Invalid parameters `cache.put` will throw an error if: * The `request` passed is a method other than `GET`. * The `response` passed has a `status` of [206 Partial Content ↗](https://www.webfx.com/web-development/glossary/http-status-codes/what-is-a-206-status-code/). * The `response` passed contains the header `Vary: *`. The value of the `Vary` header is an asterisk (`*`). Refer to the [Cache API specification ↗](https://w3c.github.io/ServiceWorker/#cache-put) for more information. #### Errors `cache.put` returns a `413` error if `Cache-Control` instructs not to cache or if the response is too large. ### `Match` JavaScript ``` cache.match(request, options); ``` * `match(request, options)` : Promise`` * Returns a promise wrapping the response object keyed to that request. Note The `stale-while-revalidate` and `stale-if-error` directives are not supported when using the `cache.put` or `cache.match` methods. #### Parameters * `request` string | Request * The string or [Request](https://developers.cloudflare.com/workers/runtime-apis/request/) object used as the lookup key. Strings are interpreted as the URL for a new `Request` object. * `options` * Can contain one possible property: `ignoreMethod` (Boolean). When `true`, the request is considered to be a `GET` request regardless of its actual value. Unlike the browser Cache API, Cloudflare Workers do not support the `ignoreSearch` or `ignoreVary` options on `match()`. You can accomplish this behavior by removing query strings or HTTP headers at `put()` time. Our implementation of the Cache API respects the following HTTP headers on the request passed to `match()`: * `Range` * Results in a `206` response if a matching response with a Content-Length header is found. Your Cloudflare cache always respects range requests, even if an `Accept-Ranges` header is on the response. * `If-Modified-Since` * Results in a `304` response if a matching response is found with a `Last-Modified` header with a value before the time specified in `If-Modified-Since`. * `If-None-Match` * Results in a `304` response if a matching response is found with an `ETag` header with a value that matches a value in `If-None-Match`. Note `cache.match()` never sends a subrequest to the origin. If no matching response is found in cache, the promise that `cache.match()` returns is fulfilled with `undefined`. #### Errors `cache.match` generates a `504` error response when the requested content is missing or expired. The Cache API does not expose this `504` directly to the Worker script, instead returning `undefined`. Nevertheless, the underlying `504` is still visible in Cloudflare Logs. If you use Cloudflare Logs, you may see these `504` responses with the `RequestSource` of `edgeWorkerCacheAPI`. Again, these are expected if the cached asset was missing or expired. Note that `edgeWorkerCacheAPI` requests are already filtered out in other views, such as Cache Analytics. To filter out these requests or to filter requests by end users of your website only, refer to [Filter end users](https://developers.cloudflare.com/analytics/graphql-api/features/filtering/#filter-end-users). ### `Delete` JavaScript ``` cache.delete(request, options); ``` * `delete(request, options)` : Promise`` Deletes the `Response` object from the cache and returns a `Promise` for a Boolean response: * `true`: The response was cached but is now deleted * `false`: The response was not in the cache at the time of deletion. Global purges The `cache.delete` method only purges content of the cache in the data center that the Worker was invoked. For global purges, refer to [Purging assets stored with the Cache API](https://developers.cloudflare.com/workers/reference/how-the-cache-works/#purge-assets-stored-with-the-cache-api). #### Parameters * `request` string | Request * The string or [Request](https://developers.cloudflare.com/workers/runtime-apis/request/) object used as the lookup key. Strings are interpreted as the URL for a new `Request` object. * `options` object * Can contain one possible property: `ignoreMethod` (Boolean). Consider the request method a GET regardless of its actual value. --- ## Related resources * [How the cache works](https://developers.cloudflare.com/workers/reference/how-the-cache-works/) * [Example: Cache using fetch()](https://developers.cloudflare.com/workers/examples/cache-using-fetch/) * [Example: using the Cache API](https://developers.cloudflare.com/workers/examples/cache-api/) * [Example: caching POST requests](https://developers.cloudflare.com/workers/examples/cache-post-request/) ```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/runtime-apis/","name":"Runtime APIs"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/runtime-apis/cache/","name":"Cache"}}]} ``` --- --- title: Console description: Supported methods of the `console` API in Cloudflare Workers image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/runtime-apis/console.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Console The `console` object provides a set of methods to help you emit logs, warnings, and debug code. All standard [methods of the console API ↗](https://developer.mozilla.org/en-US/docs/Web/API/console) are present on the `console` object in Workers. However, some methods are no ops — they can be called, and do not emit an error, but do not do anything. This ensures compatibility with libraries which may use these APIs. The table below enumerates each method, and the extent to which it is supported in Workers. All methods noted as "✅ supported" have the following behavior: * They will be written to the console in local dev (`npx wrangler@latest dev`) * They will appear in live logs, when tailing logs in the dashboard or running [wrangler tail ↗](https://developers.cloudflare.com/workers/observability/log-from-workers/#use-wrangler-tail) * They will create entries in the `logs` field of [Tail Worker ↗](https://developers.cloudflare.com/workers/observability/tail-workers/) events and [Workers Trace Events ↗](https://developers.cloudflare.com/logs/logpush/logpush-job/datasets/account/workers%5Ftrace%5Fevents/), which can be pushed to a destination of your choice via [Logpush ↗](https://developers.cloudflare.com/workers/observability/logpush/). All methods noted as "🟡 partial support" have the following behavior: * In both production and local development the method can be safely called, but will do nothing (no op) * In the [Workers Playground ↗](https://workers.cloudflare.com/playground), Quick Editor in the Workers dashboard, and remote preview mode (`wrangler dev --remote`) calling the method will behave as expected, print to the console, etc. Refer to [Log from Workers ↗](https://developers.cloudflare.com/workers/observability/log-from-workers/) for more on debugging and adding logs to Workers. | Method | Behavior | | -------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- | | [console.debug() ↗](https://developer.mozilla.org/en-US/docs/Web/API/console/debug%5Fstatic) | ✅ supported | | [console.error() ↗](https://developer.mozilla.org/en-US/docs/Web/API/console/error%5Fstatic) | ✅ supported | | [console.info() ↗](https://developer.mozilla.org/en-US/docs/Web/API/console/info%5Fstatic) | ✅ supported | | [console.log() ↗](https://developer.mozilla.org/en-US/docs/Web/API/console/log%5Fstatic) | ✅ supported | | [console.warn() ↗](https://developer.mozilla.org/en-US/docs/Web/API/console/warn%5Fstatic) | ✅ supported | | [console.clear() ↗](https://developer.mozilla.org/en-US/docs/Web/API/console/clear%5Fstatic) | 🟡 partial support | | [console.count() ↗](https://developer.mozilla.org/en-US/docs/Web/API/console/count%5Fstatic) | 🟡 partial support | | [console.group() ↗](https://developer.mozilla.org/en-US/docs/Web/API/console/group%5Fstatic) | 🟡 partial support | | [console.table() ↗](https://developer.mozilla.org/en-US/docs/Web/API/console/table%5Fstatic) | 🟡 partial support | | [console.trace() ↗](https://developer.mozilla.org/en-US/docs/Web/API/console/trace%5Fstatic) | 🟡 partial support | | [console.assert() ↗](https://developer.mozilla.org/en-US/docs/Web/API/console/assert%5Fstatic) | ⚪ no op | | [console.countReset() ↗](https://developer.mozilla.org/en-US/docs/Web/API/console/countreset%5Fstatic) | ⚪ no op | | [console.dir() ↗](https://developer.mozilla.org/en-US/docs/Web/API/console/dir%5Fstatic) | ⚪ no op | | [console.dirxml() ↗](https://developer.mozilla.org/en-US/docs/Web/API/console/dirxml%5Fstatic) | ⚪ no op | | [console.groupCollapsed() ↗](https://developer.mozilla.org/en-US/docs/Web/API/console/groupcollapsed%5Fstatic) | ⚪ no op | | [console.groupEnd ↗](https://developer.mozilla.org/en-US/docs/Web/API/console/groupend%5Fstatic) | ⚪ no op | | [console.profile() ↗](https://developer.mozilla.org/en-US/docs/Web/API/console/profile%5Fstatic) | ⚪ no op | | [console.profileEnd() ↗](https://developer.mozilla.org/en-US/docs/Web/API/console/profileend%5Fstatic) | ⚪ no op | | [console.time() ↗](https://developer.mozilla.org/en-US/docs/Web/API/console/time%5Fstatic) | ⚪ no op | | [console.timeEnd() ↗](https://developer.mozilla.org/en-US/docs/Web/API/console/timeend%5Fstatic) | ⚪ no op | | [console.timeLog() ↗](https://developer.mozilla.org/en-US/docs/Web/API/console/timelog%5Fstatic) | ⚪ no op | | [console.timeStamp() ↗](https://developer.mozilla.org/en-US/docs/Web/API/console/timestamp%5Fstatic) | ⚪ no op | | [console.createTask() ↗](https://developer.chrome.com/blog/devtools-modern-web-debugging/#linked-stack-traces) | 🔴 Will throw an exception in production, but works in local dev, Quick Editor, and remote preview | ```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/runtime-apis/","name":"Runtime APIs"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/runtime-apis/console/","name":"Console"}}]} ``` --- --- title: Context (ctx) description: The Context API in Cloudflare Workers, including props, exports, waitUntil and passThroughOnException. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/runtime-apis/context.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Context (ctx) The Context API provides methods to manage the lifecycle of your Worker or Durable Object. Context is exposed via the following places: * As the third parameter in all [handlers](https://developers.cloudflare.com/workers/runtime-apis/handlers/), including the [fetch() handler](https://developers.cloudflare.com/workers/runtime-apis/handlers/fetch/). (`fetch(request, env, ctx)`) * As a class property of the [WorkerEntrypoint class](https://developers.cloudflare.com/workers/runtime-apis/bindings/service-bindings/rpc) (`this.ctx`) Note that the Context API is available strictly in stateless contexts, that is, not [Durable Objects](https://developers.cloudflare.com/durable-objects/). However, Durable Objects have a different object, the [Durable Object State](https://developers.cloudflare.com/durable-objects/api/state/), which is available as `this.ctx` inside a Durable Object class, and provides some of the same functionality as the Context API. ## `props` `ctx.props` provides a way to pass additional configuration to a worker based on the context in which it was invoked. For example, when your Worker is called by another Worker, `ctx.props` can provide information about the calling worker. For example, imagine that you are configuring a Worker called "frontend-worker", which must talk to another Worker called "doc-worker" in order to manipulate documents. You might configure "frontend-worker" with a [Service Binding](https://developers.cloudflare.com/workers/runtime-apis/bindings/service-bindings) like: * [ wrangler.jsonc ](#tab-panel-7748) * [ wrangler.toml ](#tab-panel-7749) JSONC ``` { "services": [ { "binding": "DOC_SERVICE", "service": "doc-worker", "entrypoint": "DocServiceApi", "props": { "clientId": "frontend-worker", "permissions": [ "read", "write" ] } } ] } ``` Explain Code TOML ``` [[services]] binding = "DOC_SERVICE" service = "doc-worker" entrypoint = "DocServiceApi" [services.props] clientId = "frontend-worker" permissions = [ "read", "write" ] ``` Now frontend-worker can make calls to doc-worker with code like `env.DOC_SERVICE.getDoc(id)`. This will make a [Remote Procedure Call](https://developers.cloudflare.com/workers/runtime-apis/rpc/) invoking the method `getDoc()` of the class `DocServiceApi`, a [WorkerEntrypoint class](https://developers.cloudflare.com/workers/runtime-apis/bindings/service-bindings/rpc) exported by doc-worker. The configuration contains a `props` value. This in an arbitrary JSON value. When the `DOC_SERVICE` binding is used, the `DocServiceApi` instance receiving the call will be able to access this `props` value as `this.ctx.props`. Here, we've configured `props` to specify that the call comes from frontend-worker, and that it should be allowed to read and write documents. However, the contents of `props` can be anything you want. The Workers platform is designed to ensure that `ctx.props` can only be set by someone who has permission to edit and deploy the worker to which it is being delivered. This means that you can trust that the content of `ctx.props` is authentic. There is no need to use secret keys or cryptographic signatures in a `ctx.props` value. `ctx.props` can also be used to configure an RPC interface to represent a _specific_ resource, thus creating a "custom binding". For example, we could configure a Service Binding to our "doc-worker" which grants access only to a specific document: * [ wrangler.jsonc ](#tab-panel-7750) * [ wrangler.toml ](#tab-panel-7751) JSONC ``` { "services": [ { "binding": "FOO_DOCUMENT", "service": "doc-worker", "entrypoint": "DocumentApi", "props": { "docId": "e366592caec1d88dff724f74136b58b5", "permissions": [ "read", "write" ] } } ] } ``` Explain Code TOML ``` [[services]] binding = "FOO_DOCUMENT" service = "doc-worker" entrypoint = "DocumentApi" [services.props] docId = "e366592caec1d88dff724f74136b58b5" permissions = [ "read", "write" ] ``` Here, we've placed a `docId` property in `ctx.props`. The `DocumentApi` class could be designed to provide an API to the specific document identified by `ctx.props.docId`, and enforcing the given permissions. ## `exports` Compatibility flag required To use `ctx.exports`, you must use [the enable\_ctx\_exports compatibility flag](https://developers.cloudflare.com/workers/configuration/compatibility-flags#enable-ctxexports). `ctx.exports` provides automatically-configured "loopback" bindings for all of your top-level exports. * For each top-level export that `extends WorkerEntrypoint` (or simply implements a fetch handler), `ctx.exports` automatically contains a [Service Binding](https://developers.cloudflare.com/workers/runtime-apis/bindings/service-bindings). * For each top-level export that `extends DurableObject` (and which has been configured with storage via a [migration](https://developers.cloudflare.com/durable-objects/reference/durable-objects-migrations/)), `ctx.exports` automatically contains a [Durable Object namespace binding](https://developers.cloudflare.com/durable-objects/api/namespace/). For example: JavaScript ``` import { WorkerEntrypoint } from "cloudflare:workers"; export class Greeter extends WorkerEntrypoint { greet(name) { return `Hello, ${name}!`; } } export default { async fetch(request, env, ctx) { let greeting = await ctx.exports.Greeter.greet("World"); return new Response(greeting); }, }; ``` Explain Code In this example, the default fetch handler calls the `Greeter` class over RPC, like how you'd use a Service Binding. However, there is no external configuration required. `ctx.exports` is populated _automatically_ from your top-level imports. ### Specifying `ctx.props` when using `ctx.exports` Loopback Service Bindings in `ctx.exports` have an extra capability that regular Service Bindings do not: the caller can specify the value of `ctx.props` that should be delivered to the callee. * [ JavaScript ](#tab-panel-7746) * [ TypeScript ](#tab-panel-7747) JavaScript ``` import { WorkerEntrypoint } from "cloudflare:workers"; export class Greeter extends WorkerEntrypoint { greet(name) { return `${this.ctx.props.greeting}, ${name}!`; } } export default { async fetch(request, env, ctx) { // Make a custom greeter that uses the greeting "Welcome". let greeter = ctx.exports.Greeter({ props: { greeting: "Welcome" } }); // Greet the world. Returns "Welcome, World!" let greeting = await greeter.greet("World"); return new Response(greeting); }, }; ``` Explain Code TypeScript ``` import { WorkerEntrypoint } from "cloudflare:workers"; type Props = { greeting: string; }; export class Greeter extends WorkerEntrypoint { greet(name) { return `${this.ctx.props.greeting}, ${name}!`; } } export default { async fetch(request, env, ctx) { // Make a custom greeter that uses the greeting "Welcome". let greeter = ctx.exports.Greeter({ props: { greeting: "Welcome" } }); // Greet the world. Returns "Welcome, World!" let greeting = await greeter.greet("World"); return new Response(greeting); }, } satisfies ExportedHandler; ``` Explain Code Specifying props dynamically is permitted in this case because the caller is the same Worker, and thus can be presumed to be trusted to specify any props. The ability to customize props is particularly useful when the resulting binding is to be passed to another Worker over RPC or used in the `env` of a [dynamically-loaded worker](https://developers.cloudflare.com/workers/runtime-apis/bindings/worker-loader/). Note that `props` values specified in this way are allowed to contain any "persistently" serializable type. This includes all basic [structured clonable data types ↗](https://developer.mozilla.org/en-US/docs/Web/API/Web%5FWorkers%5FAPI/Structured%5Fclone%5Falgorithm). It also includes Service Bindings themselves: you can place a Service Binding into the `props` of another Service Binding. ### TypeScript types for `ctx.exports` and `ctx.props` If using TypeScript, you should use [the wrangler types command](https://developers.cloudflare.com/workers/wrangler/commands/general/#types) to auto-generate types for your project. The generated types will ensure `ctx.exports` is typed correctly. When declaring an entrypoint class that accepts `props`, make sure to declare it as `extends WorkerEntrypoint`, where `Props` is the type of `ctx.props`. See the example above. ## `waitUntil` `ctx.waitUntil()` extends the lifetime of your Worker, allowing you to perform work without blocking returning a response, and that may continue after a response is returned. It accepts a `Promise`, which the Workers runtime will continue executing, even after a response has been returned by the Worker's [handler](https://developers.cloudflare.com/workers/runtime-apis/handlers/). `waitUntil` is commonly used to: * Fire off events to external analytics providers. (note that when you use [Workers Analytics Engine](https://developers.cloudflare.com/analytics/analytics-engine/), you do not need to use `waitUntil`) * Put items into cache using the [Cache API](https://developers.cloudflare.com/workers/runtime-apis/cache/) `waitUntil` has a 30-second time limit The Worker's lifetime is extended for up to 30 seconds after the response is sent or the client disconnects. This time limit is shared across all `waitUntil()` calls within the same request — if any Promises have not settled after 30 seconds, they are cancelled. When `waitUntil` tasks are cancelled, the following warning will be logged to [Workers Logs](https://developers.cloudflare.com/workers/observability/logs/workers-logs/) and any attached [Tail Workers](https://developers.cloudflare.com/workers/observability/logs/tail-workers/): `waitUntil() tasks did not complete within the allowed time after invocation end and have been cancelled.` If you need to guarantee that work completes successfully, you should send messages to a [Queue](https://developers.cloudflare.com/queues/) and process them in a separate consumer Worker. Queues provide reliable delivery and automatic retries, ensuring your work is not lost. Alternatives to waitUntil If you are using `waitUntil()` to emit logs or exceptions, we recommend using [Tail Workers](https://developers.cloudflare.com/workers/observability/logs/tail-workers/) instead. Even if your Worker throws an uncaught exception, the Tail Worker will execute, ensuring that you can emit logs or exceptions regardless of the Worker's invocation status. [Cloudflare Queues](https://developers.cloudflare.com/queues/) is purpose-built for performing work out-of-band, without blocking returning a response back to the client Worker. You can call `waitUntil()` multiple times. Similar to `Promise.allSettled`, even if a promise passed to one `waitUntil` call is rejected, promises passed to other `waitUntil()` calls will still continue to execute. For example: JavaScript ``` export default { async fetch(request, env, ctx) { // Forward / proxy original request let res = await fetch(request); // Add custom header(s) res = new Response(res.body, res); res.headers.set("x-foo", "bar"); // Cache the response // NOTE: Does NOT block / wait ctx.waitUntil(caches.default.put(request, res.clone())); // Done return res; }, }; ``` Explain Code ## `passThroughOnException` Reuse of body The Workers Runtime uses streaming for request and response bodies. It does not buffer the body. Hence, if an exception occurs after the body has been consumed, `passThroughOnException()` cannot send the body again. If this causes issues, we recommend cloning the request body and handling exceptions in code. This will protect against uncaught code exceptions. However some exception times such as exceed CPU or memory limits will not be mitigated. The `passThroughOnException` method allows a Worker to [fail open ↗](https://community.microfocus.com/cyberres/b/sws-22/posts/security-fundamentals-part-1-fail-open-vs-fail-closed), and pass a request through to an origin server when a Worker throws an unhandled exception. This can be useful when using Workers as a layer in front of an existing service, allowing the service behind the Worker to handle any unexpected error cases that arise in your Worker. JavaScript ``` export default { async fetch(request, env, ctx) { // Proxy to origin on unhandled/uncaught exceptions ctx.passThroughOnException(); throw new Error("Oops"); }, }; ``` ```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/runtime-apis/","name":"Runtime APIs"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/runtime-apis/context/","name":"Context (ctx)"}}]} ``` --- --- title: Encoding description: Takes a stream of code points as input and emits a stream of bytes. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/runtime-apis/encoding.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Encoding ## TextEncoder ### Background The `TextEncoder` takes a stream of code points as input and emits a stream of bytes. Encoding types passed to the constructor are ignored and a UTF-8 `TextEncoder` is created. [TextEncoder() ↗](https://developer.mozilla.org/en-US/docs/Web/API/TextEncoder/TextEncoder) returns a newly constructed `TextEncoder` that generates a byte stream with UTF-8 encoding. `TextEncoder` takes no parameters and throws no exceptions. ### Constructor JavaScript ``` let encoder = new TextEncoder(); ``` ### Properties * `encoder.encoding` DOMString read-only * The name of the encoder as a string describing the method the `TextEncoder` uses (always `utf-8`). ### Methods * `encode(inputUSVString)` : Uint8Array * Encodes a string input. --- ## TextDecoder ### Background The `TextDecoder` interface represents a UTF-8 decoder. Decoders take a stream of bytes as input and emit a stream of code points. [TextDecoder() ↗](https://developer.mozilla.org/en-US/docs/Web/API/TextDecoder/TextDecoder) returns a newly constructed `TextDecoder` that generates a code-point stream. ### Constructor JavaScript ``` let decoder = new TextDecoder(); ``` ### Properties * `decoder.encoding` DOMString read-only * The name of the decoder that describes the method the `TextDecoder` uses. * `decoder.fatal` boolean read-only * Indicates if the error mode is fatal. * `decoder.ignoreBOM` boolean read-only * Indicates if the byte-order marker is ignored. ### Methods * `decode()` : DOMString * Decodes using the method specified in the `TextDecoder` object. Learn more at [MDN’s TextDecoder documentation ↗](https://developer.mozilla.org/en-US/docs/Web/API/TextDecoder/decode). ```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/runtime-apis/","name":"Runtime APIs"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/runtime-apis/encoding/","name":"Encoding"}}]} ``` --- --- title: EventSource description: EventSource is a server-sent event API that allows a server to push events to a client. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/runtime-apis/eventsource.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # EventSource ## Background The [EventSource ↗](https://developer.mozilla.org/en-US/docs/Web/API/EventSource) interface is a server-sent event API that allows a server to push events to a client. The `EventSource` object is used to receive server-sent events. It connects to a server over HTTP and receives events in a text-based format. ### Constructor JavaScript ``` let eventSource = new EventSource(url, options); ``` * `url` USVString - The URL to which to connect. * `options` EventSourceInit - An optional dictionary containing any optional settings. By default, the `EventSource` will use the global `fetch()` function under the covers to make requests. If you need to use a different fetch implementation as provided by a Cloudflare Workers binding, you can pass the `fetcher` option: JavaScript ``` export default { async fetch(req, env) { let eventSource = new EventSource(url, { fetcher: env.MYFETCHER }); // ... } }; ``` Note that the `fetcher` option is a Cloudflare Workers specific extension. ### Properties * `eventSource.url` USVString read-only * The URL of the event source. * `eventSource.readyState` USVString read-only * The state of the connection. * `eventSource.withCredentials` Boolean read-only * A Boolean indicating whether the `EventSource` object was instantiated with cross-origin (CORS) credentials set (`true`), or not (`false`). ### Methods * `eventSource.close()` * Closes the connection. * `eventSource.onopen` * An event handler called when a connection is opened. * `eventSource.onmessage` * An event handler called when a message is received. * `eventSource.onerror` * An event handler called when an error occurs. ### Events * `message` * Fired when a message is received. * `open` * Fired when the connection is opened. * `error` * Fired when an error occurs. ### Class Methods * `EventSource.from(readableStreamReadableStream) : EventSource` * This is a Cloudflare Workers specific extension that creates a new `EventSource` object from an existing `ReadableStream`. Such an instance does not initiate a new connection but instead attaches to the provided stream. ```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/runtime-apis/","name":"Runtime APIs"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/runtime-apis/eventsource/","name":"EventSource"}}]} ``` --- --- title: Fetch description: An interface for asynchronously fetching resources via HTTP requests inside of a Worker. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/runtime-apis/fetch.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Fetch The [Fetch API ↗](https://developer.mozilla.org/en-US/docs/Web/API/Fetch%5FAPI) provides an interface for asynchronously fetching resources via HTTP requests inside of a Worker. Note Asynchronous tasks such as `fetch` must be executed within a [handler](https://developers.cloudflare.com/workers/runtime-apis/handlers/). If you try to call `fetch()` within [global scope ↗](https://developer.mozilla.org/en-US/docs/Glossary/Global%5Fscope), your Worker will throw an error. Learn more about [the Request context](https://developers.cloudflare.com/workers/runtime-apis/request/#the-request-context). Worker to Worker Worker-to-Worker `fetch` requests are possible with [Service bindings](https://developers.cloudflare.com/workers/runtime-apis/bindings/service-bindings/) or by enabling the [global\_fetch\_strictly\_public compatibility flag](https://developers.cloudflare.com/workers/configuration/compatibility-flags/#global-fetch-strictly-public). ## Syntax * [ Module Worker ](#tab-panel-7752) * [ Service Worker ](#tab-panel-7753) * [ Python Worker ](#tab-panel-7754) JavaScript ``` export default { async scheduled(controller, env, ctx) { return await fetch("https://example.com", { headers: { "X-Source": "Cloudflare-Workers", }, }); }, }; ``` Service Workers are deprecated Service Workers are deprecated, but still supported. We recommend using [Module Workers](https://developers.cloudflare.com/workers/reference/migrate-to-module-workers/) instead. New features may not be supported for Service Workers. JavaScript ``` addEventListener("fetch", (event) => { // NOTE: can’t use fetch here, as we’re not in an async scope yet event.respondWith(eventHandler(event)); }); async function eventHandler(event) { // fetch can be awaited here since `event.respondWith()` waits for the Promise it receives to settle const resp = await fetch(event.request); return resp; } ``` Explain Code Python ``` from workers import WorkerEntrypoint, Response, fetch class Default(WorkerEntrypoint): async def scheduled(self, controller, env, ctx): return await fetch("https://example.com", headers={"X-Source": "Cloudflare-Workers"}) ``` * `fetch(resource, options optional)` : Promise`` * Fetch returns a promise to a Response. ### Parameters * [resource ↗](https://developer.mozilla.org/en-US/docs/Web/API/fetch#resource) Request | string | URL * `options` options * `cache` `undefined | 'no-store' | 'no-cache'` optional * Standard HTTP `cache` header. Only `cache: 'no-store'` and `cache: 'no-cache'` are supported. Any other `cache` header will result in a `TypeError` with the message `Unsupported cache mode: `. \_ For all requests this forwards the `Pragma: no-cache` and `Cache-Control: no-cache` headers to the origin. \_ For `no-store`, requests to origins not hosted by Cloudflare bypass the use of Cloudflare's caches. \_ For `no-cache`, requests to origins not hosted by Cloudflare are forced to revalidate with the origin before resonding. * An object that defines the content and behavior of the request. --- ## How the `Accept-Encoding` header is handled When making a subrequest with the `fetch()` API, you can specify which forms of compression to prefer that the server will respond with (if the server supports it) by including the [Accept-Encoding ↗](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Accept-Encoding) header. Workers supports both the gzip and brotli compression algorithms. Usually it is not necessary to specify `Accept-Encoding` or `Content-Encoding` headers in the Workers Runtime production environment – brotli or gzip compression is automatically requested when fetching from an origin and applied to the response when returning data to the client, depending on the capabilities of the client and origin server. To support requesting brotli from the origin, you must enable the [brotli\_content\_encoding](https://developers.cloudflare.com/workers/configuration/compatibility-flags/#brotli-content-encoding-support) compatibility flag in your Worker. Soon, this compatibility flag will be enabled by default for all Workers past an upcoming compatibility date. ### Passthrough behavior One scenario where the Accept-Encoding header is useful is for passing through compressed data from a server to the client, where the Accept-Encoding allows the worker to directly receive the compressed data stream from the server without it being decompressed beforehand. As long as you do not read the body of the compressed response prior to returning it to the client and keep the `Content-Encoding` header intact, it will "pass through" without being decompressed and then recompressed again. This can be helpful when using Workers in front of origin servers or when fetching compressed media assets, to ensure that the same compression used by the origin server is used in the response that your Worker returns. In addition to a change in the content encoding, recompression is also needed when a response uses an encoding not supported by the client. As an example, when a Worker requests either brotli or gzip as the encoding but the client only supports gzip, recompression will still be needed if the server returns brotli-encoded data to the server (and will be applied automatically). Note that this behavior may also vary based on the [compression rules](https://developers.cloudflare.com/rules/compression-rules/), which can be used to configure what compression should be applied for different types of data on the server side. TypeScript ``` export default { async fetch(request) { // Accept brotli or gzip compression const headers = new Headers({ "Accept-Encoding": "br, gzip", }); let response = await fetch("https://developers.cloudflare.com", { method: "GET", headers, }); // As long as the original response body is returned and the Content-Encoding header is // preserved, the same encoded data will be returned without needing to be compressed again. return new Response(response.body, { status: response.status, statusText: response.statusText, headers: response.headers, }); }, }; ``` Explain Code ## Related resources * [Example: use fetch to respond with another site](https://developers.cloudflare.com/workers/examples/respond-with-another-site/) * [Example: Fetch HTML](https://developers.cloudflare.com/workers/examples/fetch-html/) * [Example: Fetch JSON](https://developers.cloudflare.com/workers/examples/fetch-json/) * [Example: cache using Fetch](https://developers.cloudflare.com/workers/examples/cache-using-fetch/) * Write your Worker code in [ES modules syntax](https://developers.cloudflare.com/workers/reference/migrate-to-module-workers/) for an optimized experience. * [Error 526](https://developers.cloudflare.com/support/troubleshooting/http-status-codes/cloudflare-5xx-errors/error-526/#error-526-in-the-workers-context) * [Fetch API in a partial setup](https://developers.cloudflare.com/workers/platform/known-issues/#fetch-api-in-cname-setup) ```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/runtime-apis/","name":"Runtime APIs"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/runtime-apis/fetch/","name":"Fetch"}}]} ``` --- --- title: Handlers description: Methods, such as `fetch()`, on Workers that can receive and process external inputs. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/runtime-apis/handlers/index.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Handlers Handlers are methods on Workers that can receive and process external inputs, and can be invoked from outside your Worker. For example, the `fetch()` handler receives an HTTP request, and can return a response: JavaScript ``` export default { async fetch(request, env, ctx) { return new Response('Hello World!'); }, }; ``` The following handlers are available within Workers: * [ Alarm Handler ](https://developers.cloudflare.com/durable-objects/api/alarms/) * [ Email Handler ](https://developers.cloudflare.com/email-routing/email-workers/runtime-api/) * [ Fetch Handler ](https://developers.cloudflare.com/workers/runtime-apis/handlers/fetch/) * [ Queue Handler ](https://developers.cloudflare.com/queues/configuration/javascript-apis/#consumer) * [ Scheduled Handler ](https://developers.cloudflare.com/workers/runtime-apis/handlers/scheduled/) * [ Tail Handler ](https://developers.cloudflare.com/workers/runtime-apis/handlers/tail/) ## Handlers in Python Workers When you [write Workers in Python](https://developers.cloudflare.com/workers/languages/python/), handlers are placed in a class named `Default` that extends the [WorkerEntrypoint class](https://developers.cloudflare.com/workers/runtime-apis/bindings/service-bindings/rpc/) (which you can import from the `workers` SDK module). ```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/runtime-apis/","name":"Runtime APIs"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/runtime-apis/handlers/","name":"Handlers"}}]} ``` --- --- title: Alarm Handler image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/runtime-apis/handlers/alarm.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Alarm Handler ```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/runtime-apis/","name":"Runtime APIs"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/runtime-apis/handlers/","name":"Handlers"}},{"@type":"ListItem","position":5,"item":{"@id":"/workers/runtime-apis/handlers/alarm/","name":"Alarm Handler"}}]} ``` --- --- title: Email Handler image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/runtime-apis/handlers/email.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Email Handler ```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/runtime-apis/","name":"Runtime APIs"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/runtime-apis/handlers/","name":"Handlers"}},{"@type":"ListItem","position":5,"item":{"@id":"/workers/runtime-apis/handlers/email/","name":"Email Handler"}}]} ``` --- --- title: Fetch Handler description: Incoming HTTP requests to a Worker are passed to the fetch() handler as a Request object. To respond to the request with a response, return a Response object: image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/runtime-apis/handlers/fetch.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Fetch Handler ## Background Incoming HTTP requests to a Worker are passed to the `fetch()` handler as a [Request](https://developers.cloudflare.com/workers/runtime-apis/request/) object. To respond to the request with a response, return a [Response](https://developers.cloudflare.com/workers/runtime-apis/response/) object: JavaScript ``` export default { async fetch(request, env, ctx) { return new Response('Hello World!'); }, }; ``` Note The Workers runtime does not support `XMLHttpRequest` (XHR). Learn the difference between `XMLHttpRequest` and `fetch()` in the [MDN ↗](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest) documentation. ### Parameters * `request` Request * The incoming HTTP request. * `env` object * The [bindings](https://developers.cloudflare.com/workers/runtime-apis/bindings/) available to the Worker. As long as the [environment](https://developers.cloudflare.com/workers/wrangler/environments/) has not changed, the same object (equal by identity) may be passed to multiple requests. You can also [import env from cloudflare:workers](https://developers.cloudflare.com/workers/runtime-apis/bindings/#importing-env-as-a-global) to access bindings from anywhere in your code. * `ctx.waitUntil(promisePromise)` : void * Refer to [waitUntil](https://developers.cloudflare.com/workers/runtime-apis/context/#waituntil). * `ctx.passThroughOnException()` : void * Refer to [passThroughOnException](https://developers.cloudflare.com/workers/runtime-apis/context/#passthroughonexception). ```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/runtime-apis/","name":"Runtime APIs"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/runtime-apis/handlers/","name":"Handlers"}},{"@type":"ListItem","position":5,"item":{"@id":"/workers/runtime-apis/handlers/fetch/","name":"Fetch Handler"}}]} ``` --- --- title: Queue Handler image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/runtime-apis/handlers/queue.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Queue Handler ```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/runtime-apis/","name":"Runtime APIs"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/runtime-apis/handlers/","name":"Handlers"}},{"@type":"ListItem","position":5,"item":{"@id":"/workers/runtime-apis/handlers/queue/","name":"Queue Handler"}}]} ``` --- --- title: Scheduled Handler description: When a Worker is invoked via a Cron Trigger, the scheduled() handler handles the invocation. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/runtime-apis/handlers/scheduled.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Scheduled Handler ## Background When a Worker is invoked via a [Cron Trigger](https://developers.cloudflare.com/workers/configuration/cron-triggers/), the `scheduled()` handler handles the invocation. Testing scheduled() handlers in local development You can test the behavior of your `scheduled()` handler in local development using Wrangler. Cron Triggers can be tested using `Wrangler` by passing in the `--test-scheduled` flag to [wrangler dev](https://developers.cloudflare.com/workers/wrangler/commands/general/#dev). This will expose a `/__scheduled` (or `/cdn-cgi/handler/scheduled` for Python Workers) route which can be used to test using a http request. To simulate different cron patterns, a `cron` query parameter can be passed in. Terminal window ``` npx wrangler dev --test-scheduled curl "http://localhost:8787/__scheduled?cron=*+*+*+*+*" curl "http://localhost:8787/cdn-cgi/handler/scheduled?cron=*+*+*+*+*" # Python Workers ``` --- ## Syntax * [ JavaScript ](#tab-panel-7755) * [ TypeScript ](#tab-panel-7756) * [ Python ](#tab-panel-7757) JavaScript ``` export default { async scheduled(controller, env, ctx) { ctx.waitUntil(doSomeTaskOnASchedule()); }, }; ``` TypeScript ``` interface Env {} export default { async scheduled( controller: ScheduledController, env: Env, ctx: ExecutionContext, ) { ctx.waitUntil(doSomeTaskOnASchedule()); }, }; ``` Explain Code Python ``` from workers import WorkerEntrypoint, Response, fetch class Default(WorkerEntrypoint): async def scheduled(self, controller, env, ctx): ctx.waitUntil(doSomeTaskOnASchedule()) ``` ### Properties * `controller.cron` string * The value of the [Cron Trigger](https://developers.cloudflare.com/workers/configuration/cron-triggers/) that started the `ScheduledEvent`. * `controller.type` string * The type of controller. This will always return `"scheduled"`. * `controller.scheduledTime` number * The time the `ScheduledEvent` was scheduled to be executed in milliseconds since January 1, 1970, UTC. It can be parsed as `new Date(controller.scheduledTime)`. * `env` object * An object containing the bindings associated with your Worker using ES modules format, such as KV namespaces and Durable Objects. * `ctx` object * An object containing the context associated with your Worker using ES modules format. Currently, this object just contains the `waitUntil` function. ### Handle multiple cron triggers When you configure multiple [Cron Triggers](https://developers.cloudflare.com/workers/configuration/cron-triggers/) for a single Worker, each trigger invokes the same `scheduled()` handler. Use `controller.cron` to distinguish which schedule fired and run different logic for each. * [ wrangler.jsonc ](#tab-panel-7760) * [ wrangler.toml ](#tab-panel-7761) JSONC ``` { "triggers": { "crons": ["*/5 * * * *", "0 0 * * *"], }, } ``` TOML ``` [triggers] crons = [ "*/5 * * * *", "0 0 * * *" ] ``` * [ JavaScript ](#tab-panel-7758) * [ TypeScript ](#tab-panel-7759) JavaScript ``` export default { async scheduled(controller, env, ctx) { switch (controller.cron) { case "*/5 * * * *": ctx.waitUntil(fetch("https://example.com/api/sync")); break; case "0 0 * * *": ctx.waitUntil(env.MY_KV.put("last-cleanup", new Date().toISOString())); break; } }, }; ``` Explain Code TypeScript ``` export default { async scheduled( controller: ScheduledController, env: Env, ctx: ExecutionContext, ) { switch (controller.cron) { case "*/5 * * * *": ctx.waitUntil(fetch("https://example.com/api/sync")); break; case "0 0 * * *": ctx.waitUntil(env.MY_KV.put("last-cleanup", new Date().toISOString())); break; } }, } satisfies ExportedHandler; ``` Explain Code The value of `controller.cron` is the exact cron expression string from your configuration. It must match character-for-character, including spacing. ### Methods When a Workers script is invoked by a [Cron Trigger](https://developers.cloudflare.com/workers/configuration/cron-triggers/), the Workers runtime starts a `ScheduledEvent` which will be handled by the `scheduled` function in your Workers Module class. The `ctx` argument represents the context your function runs in, and contains the following methods to control what happens next: * `ctx.waitUntil(promisePromise)` : void - Use this method to notify the runtime to wait for asynchronous tasks (for example, logging, analytics to third-party services, streaming and caching). The first `ctx.waitUntil` to fail will be observed and recorded as the status in the [Cron Trigger](https://developers.cloudflare.com/workers/configuration/cron-triggers/) Past Events table. Otherwise, it will be reported as a success. ```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/runtime-apis/","name":"Runtime APIs"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/runtime-apis/handlers/","name":"Handlers"}},{"@type":"ListItem","position":5,"item":{"@id":"/workers/runtime-apis/handlers/scheduled/","name":"Scheduled Handler"}}]} ``` --- --- title: Tail Handler description: The tail() handler is the handler you implement when writing a Tail Worker. Tail Workers can be used to process logs in real-time and send them to a logging or analytics service. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/runtime-apis/handlers/tail.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Tail Handler ## Background The `tail()` handler is the handler you implement when writing a [Tail Worker](https://developers.cloudflare.com/workers/observability/logs/tail-workers/). Tail Workers can be used to process logs in real-time and send them to a logging or analytics service. The `tail()` handler is called once each time the connected producer Worker is invoked. To configure a Tail Worker, refer to [Tail Workers documentation](https://developers.cloudflare.com/workers/observability/logs/tail-workers/). ## Syntax JavaScript ``` export default { async tail(events, env, ctx) { fetch("", { method: "POST", body: JSON.stringify(events), }) } } ``` ### Parameters * `events` array * An array of [TailItems](https://developers.cloudflare.com/workers/runtime-apis/handlers/tail/#tailitems). One `TailItem` is collected for each event that triggers a Worker. For Workers for Platforms customers with a Tail Worker installed on the dynamic dispatch Worker, `events` will contain two elements: one for the dynamic dispatch Worker and one for the User Worker. * `env` object * An object containing the bindings associated with your Worker using [ES modules format](https://developers.cloudflare.com/workers/reference/migrate-to-module-workers/), such as KV namespaces and Durable Objects. * `ctx` object * An object containing the context associated with your Worker using [ES modules format](https://developers.cloudflare.com/workers/reference/migrate-to-module-workers/). Currently, this object just contains the `waitUntil` function. ### Properties * `event.type` string * The type of event. This will always return `"tail"`. * `event.traces` array * An array of [TailItems](https://developers.cloudflare.com/workers/runtime-apis/handlers/tail/#tailitems). One `TailItem` is collected for each event that triggers a Worker. For Workers for Platforms customers with a Tail Worker installed on the dynamic dispatch Worker, `events` will contain two elements: one for the dynamic dispatch Worker and one for the user Worker. * `event.waitUntil(promisePromise)` : void * Refer to [waitUntil](https://developers.cloudflare.com/workers/runtime-apis/context/#waituntil). Note that unlike fetch event handlers, tail handlers do not return a value, so this is the only way for trace Workers to do asynchronous work. ### `TailItems` #### Properties * `scriptName` string * The name of the producer script. * `event` object * Contains information about the Worker’s triggering event. * For fetch events: a [FetchEventInfo object](https://developers.cloudflare.com/workers/runtime-apis/handlers/tail/#fetcheventinfo) * For other event types: `null`, currently. * `eventTimestamp` number * Measured in epoch time. * `logs` array * An array of [TailLogs](https://developers.cloudflare.com/workers/runtime-apis/handlers/tail/#taillog). * `exceptions` array * An array of [TailExceptions](https://developers.cloudflare.com/workers/runtime-apis/handlers/tail/#tailexception). A single Worker invocation might result in multiple unhandled exceptions, since a Worker can register multiple asynchronous tasks. * `outcome` string * The outcome of the Worker invocation, one of: * `unknown`: outcome status was not set. * `ok`: The worker invocation succeeded. * `exception`: An unhandled exception was thrown. This can happen for many reasons, including: * An uncaught JavaScript exception. * A fetch handler that does not result in a Response. * An internal error. * `exceededCpu`: The Worker invocation exceeded either its CPU limits. * `exceededMemory`: The Worker invocation exceeded memory limits. * `scriptNotFound`: An internal error from difficulty retrieving the Worker script. * `canceled`: The worker invocation was canceled before it completed. Commonly because the client disconnected before a response could be sent. * `responseStreamDisconnected`: The response stream was disconnected during deferred proxying. Happens when either the client or server hangs up early. Outcome is not the same as HTTP status. Outcome is equivalent to the exit status of a script and an indicator of whether it has fully run to completion. A Worker outcome may differ from a response code if, for example: * a script successfully processes a request but is logically designed to return a `4xx`/`5xx` response. * a script sends a successful `200` response but an asynchronous task registered via `waitUntil()` later exceeds CPU or memory limits. ### `FetchEventInfo` #### Properties * `request` object * A [TailRequest object](https://developers.cloudflare.com/workers/runtime-apis/handlers/tail/#tailrequest). * `response` object * A [TailResponse object](https://developers.cloudflare.com/workers/runtime-apis/handlers/tail/#tailresponse). ### `TailRequest` #### Properties * `cf` object * Contains the data from [IncomingRequestCfProperties](https://developers.cloudflare.com/workers/runtime-apis/request/#incomingrequestcfproperties). * `headers` object * Header name/value entries (redacted by default). Header names are lowercased, and the values associated with duplicate header names are concatenated, with the string `", "` (comma space) interleaved, similar to [the Fetch standard ↗](https://fetch.spec.whatwg.org/#concept-header-list-get). * `method` string * The HTTP request method. * `url` string * The HTTP request URL (redacted by default). #### Methods * `getUnredacted()` object * Returns a TailRequest object with unredacted properties Some of the properties of `TailRequest` are redacted by default to make it harder to accidentally record sensitive information, like user credentials or API tokens. The redactions use heuristic rules, so they are subject to false positives and negatives. Clients can call `getUnredacted()` to bypass redaction, but they should always be careful about what information is retained, whether using the redaction or not. * Header redaction: The header value will be the string `“REDACTED”` when the (case-insensitive) header name is `cookie`/`set-cookie` or contains a substring `"auth”`, `“key”`, `“secret”`, `“token”`, or `"jwt"`. * URL redaction: For each greedily matched substring of ID characters (a-z, A-Z, 0-9, '+', '-', '\_') in the URL, if it meets the following criteria for a hex or base-64 ID, the substring will be replaced with the string `“REDACTED”`. * Hex ID: Contains 32 or more hex digits, and contains only hex digits and separators ('+', '-', '\_') * Base-64 ID: Contains 21 or more characters, and contains at least two uppercase, two lowercase, and two digits. ### `TailResponse` #### Properties * `status` number * The HTTP status code. ### `TailLog` Records information sent to console functions. #### Properties * `timestamp` number * Measured in epoch time. * `level` string * A string indicating the console function that was called. One of: `debug`, `info`, `log`, `warn`, `error`. * `message` object * The array of parameters passed to the console function. ### `TailException` Records an unhandled exception that occurred during the Worker invocation. #### Properties * `timestamp` number * Measured in epoch time. * `name` string * The error type (For example,`Error`, `TypeError`, etc.). * `message` object * The error description (For example, `"x" is not a function`). ## Related resources * [Tail Workers](https://developers.cloudflare.com/workers/observability/logs/tail-workers/) \- Configure a Tail Worker to receive information about the execution of other Workers. ```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/runtime-apis/","name":"Runtime APIs"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/runtime-apis/handlers/","name":"Handlers"}},{"@type":"ListItem","position":5,"item":{"@id":"/workers/runtime-apis/handlers/tail/","name":"Tail Handler"}}]} ``` --- --- title: Headers description: Access HTTP request and response headers. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/runtime-apis/headers.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Headers ## Background All HTTP request and response headers are available through the [Headers API ↗](https://developer.mozilla.org/en-US/docs/Web/API/Headers). When a header name possesses multiple values, those values will be concatenated as a single, comma-delimited string value. This means that `Headers.get` will always return a string or a `null` value. This applies to all header names except for `Set-Cookie`, which requires `Headers.getAll`. This is documented below in [Differences](#differences). JavaScript ``` let headers = new Headers(); headers.get('x-foo'); //=> null headers.set('x-foo', '123'); headers.get('x-foo'); //=> "123" headers.set('x-foo', 'hello'); headers.get('x-foo'); //=> "hello" headers.append('x-foo', 'world'); headers.get('x-foo'); //=> "hello, world" ``` Explain Code ## Differences The Workers implementation of the `Headers` API differs from the web standard in several ways. These differences are intentional, and reflect the server-side nature of the Workers runtime. TypeScript users Workers type definitions (from `@cloudflare/workers-types` or generated via [wrangler types](https://developers.cloudflare.com/workers/wrangler/commands/general/#types)) define a `Headers` type that includes Workers-specific methods like `getAll()`. This type is not directly compatible with the standard `Headers` type from `lib.dom.d.ts`. If you are working with code that uses both Workers types and standard web types, you may need to use type assertions. ### `getAll()` method Despite the fact that the `Headers.getAll` method has been made obsolete in web browsers, Workers still provides this method for use with the `Set-Cookie` header. This is because cookies often contain date strings, which include commas. This can make parsing multiple values in a `Set-Cookie` header difficult. Any attempts to use `Headers.getAll` with other header names will throw an error. A brief history of `Headers.getAll` is available in this [GitHub issue ↗](https://github.com/whatwg/fetch/issues/973). ### `Set-Cookie` handling Due to [RFC 6265 ↗](https://www.rfc-editor.org/rfc/rfc6265) prohibiting folding multiple `Set-Cookie` headers into a single header, the `Headers.append` method allows you to set multiple `Set-Cookie` response headers instead of appending the value onto the existing header. JavaScript ``` const headers = new Headers(); headers.append("Set-Cookie", "cookie1=value_for_cookie_1; Path=/; HttpOnly;"); headers.append("Set-Cookie", "cookie2=value_for_cookie_2; Path=/; HttpOnly;"); console.log(headers.getAll("Set-Cookie")); // Array(2) [ cookie1=value_for_cookie_1; Path=/; HttpOnly;, cookie2=value_for_cookie_2; Path=/; HttpOnly; ] ``` ### `USVString` return type In Cloudflare Workers, the `Headers.get` method returns a [USVString ↗](https://mdn2.netlify.app/en-us/docs/web/api/usvstring/) instead of a [ByteString ↗](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global%5FObjects/String), which is specified by the web standard. For most scenarios, this should have no noticeable effect. To compare the differences between these two string classes, refer to this [Playground example ↗](https://workers.cloudflare.com/playground#LYVwNgLglgDghgJwgegGYHsHALQBM4RwDcABAEbogB2+CAngLzbMutvvsCMALAJx-cAzAHZeANkG8AHAAZOU7t2EBWAEy9eqsXNWdOALg5HjbHv34jxk2fMUr1m7Z12cAsACgAwuioQApr7YACJQAM4w6KFQ0D76JBhYeATEJFRwwH4MAERQNH4AHgB0AFahWaSoUGAB6Zk5eUWlWR7evgEQ2AAqdDB+cXAwMGBQAMYEUD7IxXAAbnChIwiwEADUwOi44H4eHgURSCS4fqhw4BAkAN7uAJDzdFQj8X4QIwAWABQIfgCOIH6hEAAlJcbtdqucxucGCQsoBeDcAXHtZUHgkggCCoKSeAgkaFUPwAdxInQKEAAog8Nn4EO9AYUAiNKe9IYDkc8SPTKbgsVCSABlCBLKgAc0KqAQ6GAnleiG8R3ehQVaIx3JZoIZVFC6GqhTA6CF7yynVeYRIJrgJAAqryAGr8wVCkj46KvEjmyH6LIAGhIzLVPk12t1+sNxtCprD5oAQnR-Hbcg6nRAXW7sT5LZ0AGLYKQe70co5cgiq67XZDIEgACT8cCOCAjXxIoRAg0iflwJAg6EdmAA1iQfGA6I7nSRo7GBfHQt6yGj+yAEKCy6bgEM-BlfOM0yBQv9LTa48LQoUiaHUiSSMM8cOwGASDBBec4Ivy-jEFR466KLOk2FCqzzq81a1mGuIEpWQFUqE7wXDC+ZttgkJZHEcGFucAC+xbXF8EDzlQZ6EgASv8EQan4BpSn4Ix9pQ5xJn4JAAAatAGfgMa6NAdoBJBEeE-r0YBNaQR2XY7vRdFzhAMCzgyK6IGE-qFF6lwkAJwEkBhNxoe4aEeCYelGGYAiWBI0hyAoShqBoWg6HoLQ+P4gQhLxUQxFQcQJDg+CEKQaQZNkGSEF5cDlPEVQ1H5WRkLqZDNF49ntF0PR9K6gzDJCExUFMmpUDs7gXFkwBwLkAD66ybNUSH1EcjRlDp7j6Q1rCGRYogmTY5n2FZTguMwHhAA). ## Cloudflare headers Cloudflare sets a number of its own custom headers on incoming requests and outgoing responses. While some may be used for its own tracking and bookkeeping, many of these can be useful to your own applications – or Workers – too. For a list of documented Cloudflare request headers, refer to [Cloudflare HTTP headers](https://developers.cloudflare.com/fundamentals/reference/http-headers/). ## Related resources * [Logging headers to console](https://developers.cloudflare.com/workers/examples/logging-headers/) \- Review how to log headers in the console. * [Cloudflare HTTP headers](https://developers.cloudflare.com/fundamentals/reference/http-headers/) \- Contains a list of specific headers that Cloudflare adds. ```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/runtime-apis/","name":"Runtime APIs"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/runtime-apis/headers/","name":"Headers"}}]} ``` --- --- title: HTMLRewriter description: Build comprehensive and expressive HTML parsers inside of a Worker application. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/runtime-apis/html-rewriter.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # HTMLRewriter ## Background The `HTMLRewriter` class allows developers to build comprehensive and expressive HTML parsers inside of a Cloudflare Workers application. It can be thought of as a jQuery-like experience directly inside of your Workers application. Leaning on a powerful JavaScript API to parse and transform HTML, `HTMLRewriter` allows developers to build deeply functional applications. The `HTMLRewriter` class should be instantiated once in your Workers script, with a number of handlers attached using the `on` and `onDocument` functions. --- ## Constructor JavaScript ``` new HTMLRewriter() .on("*", new ElementHandler()) .onDocument(new DocumentHandler()); ``` --- ## Global types Throughout the `HTMLRewriter` API, there are a few consistent types that many properties and methods use: * `Content` string | Response | ReadableStream * Content inserted in the output stream should be a string, [Response](https://developers.cloudflare.com/workers/runtime-apis/response/), or [ReadableStream](https://developers.cloudflare.com/workers/runtime-apis/streams/readablestream/). * `ContentOptions` Object * `{ html: Boolean }` Controls the way the HTMLRewriter treats inserted content. If the `html` boolean is set to true, content is treated as raw HTML. If the `html` boolean is set to false or not provided, content will be treated as text and proper HTML escaping will be applied to it. --- ## Handlers There are two handler types that can be used with `HTMLRewriter`: element handlers and document handlers. ### Element Handlers An element handler responds to any incoming element, when attached using the `.on` function of an `HTMLRewriter` instance. The element handler should respond to `element`, `comments`, and `text`. The example processes `div` elements with an `ElementHandler` class. JavaScript ``` class ElementHandler { element(element) { // An incoming element, such as `div` console.log(`Incoming element: ${element.tagName}`); } comments(comment) { // An incoming comment } text(text) { // An incoming piece of text } } async function handleRequest(req) { const res = await fetch(req); return new HTMLRewriter().on("div", new ElementHandler()).transform(res); } ``` Explain Code ### Document Handlers A document handler represents the incoming HTML document. A number of functions can be defined on a document handler to query and manipulate a document’s `doctype`, `comments`, `text`, and `end`. Unlike an element handler, a document handler’s `doctype`, `comments`, `text`, and `end` functions are not scoped by a particular selector. A document handler's functions are called for all the content on the page including the content outside of the top-level HTML tag: JavaScript ``` class DocumentHandler { doctype(doctype) { // An incoming doctype, such as } comments(comment) { // An incoming comment } text(text) { // An incoming piece of text } end(end) { // The end of the document } } ``` Explain Code #### Async Handlers All functions defined on both element and document handlers can return either `void` or a `Promise`. Making your handler function `async` allows you to access external resources such as an API via fetch, Workers KV, Durable Objects, or the cache. JavaScript ``` class UserElementHandler { async element(element) { let response = await fetch(new Request("/user")); // fill in user info using response } } async function handleRequest(req) { const res = await fetch(req); // run the user element handler via HTMLRewriter on a div with ID `user_info` return new HTMLRewriter() .on("div#user_info", new UserElementHandler()) .transform(res); } ``` Explain Code ### Element The `element` argument, used only in element handlers, is a representation of a DOM element. A number of methods exist on an element to query and manipulate it: #### Properties * `tagName` string * The name of the tag, such as `"h1"` or `"div"`. This property can be assigned different values, to modify an element’s tag. * `attributes` Iterator read-only * A `[name, value]` pair of the tag’s attributes. * `removed` boolean * Indicates whether the element has been removed or replaced by one of the previous handlers. * `namespaceURI` string * Represents the [namespace URI ↗](https://infra.spec.whatwg.org/#namespaces) of an element. #### Methods * `` getAttribute(name ` string `) `` : ` string | null ` * Returns the value for a given attribute name on the element, or `null` if it is not found. * `` hasAttribute(name ` string `) `` : ` boolean ` * Returns a boolean indicating whether an attribute exists on the element. * `` setAttribute(name ` string `, value ` string `) `` : ` Element ` * Sets an attribute to a provided value, creating the attribute if it does not exist. * `` removeAttribute(name ` string `) `` : ` Element ` * Removes the attribute. * `` before(content ` Content `, contentOptions ` ContentOptions ` optional) `` : ` Element ` * Inserts content before the element. Content and ContentOptions Refer to [Global types](https://developers.cloudflare.com/workers/runtime-apis/html-rewriter/#global-types) for more information on `Content` and `ContentOptions`. * `` after(content ` Content `, contentOptions ` ContentOptions ` optional) `` : ` Element ` * Inserts content right after the element. * `` prepend(content ` Content `, contentOptions ` ContentOptions ` optional) `` : ` Element ` * Inserts content right after the start tag of the element. * `` append(content ` Content `, contentOptions ` ContentOptions ` optional) `` : ` Element ` * Inserts content right before the end tag of the element. * `` replace(content ` Content `, contentOptions ` ContentOptions ` optional) `` : ` Element ` * Removes the element and inserts content in place of it. * `` setInnerContent(content ` Content `, contentOptions ` ContentOptions ` optional) `` : ` Element ` * Replaces content of the element. * `remove()` : ` Element ` * Removes the element with all its content. * `removeAndKeepContent()` : ` Element ` * Removes the start tag and end tag of the element but keeps its inner content intact. * `` onEndTag(handler ` Function `) `` : ` void ` * Registers a handler that is invoked when the end tag of the element is reached. ### EndTag The `endTag` argument, used only in handlers registered with `element.onEndTag`, is a limited representation of a DOM element. #### Properties * `name` string * The name of the tag, such as `"h1"` or `"div"`. This property can be assigned different values, to modify an element's tag. #### Methods * `` before(content ` Content `, contentOptions ` ContentOptions ` optional) `` : ` EndTag ` * Inserts content right before the end tag. * `` after(content ` Content `, contentOptions ` ContentOptions ` optional) `` : ` EndTag ` * Inserts content right after the end tag. Content and ContentOptions Refer to [Global types](https://developers.cloudflare.com/workers/runtime-apis/html-rewriter/#global-types) for more information on `Content` and `ContentOptions`. * `remove()` : ` EndTag ` * Removes the element with all its content. ### Text chunks Since Cloudflare performs zero-copy streaming parsing, text chunks are not the same thing as text nodes in the lexical tree. A lexical tree text node can be represented by multiple chunks, as they arrive over the wire from the origin. Consider the following markup: `
Hey. How are you?
`. It is possible that the Workers script will not receive the entire text node from the origin at once; instead, the `text` element handler will be invoked for each received part of the text node. For example, the handler might be invoked with `"Hey. How "`, then `"are you?"`. When the last chunk arrives, the text's `lastInTextNode` property will be set to `true`. Developers should make sure to concatenate these chunks together. #### Properties * `removed` boolean * Indicates whether the element has been removed or replaced by one of the previous handlers. * `text` string read-only * The text content of the chunk. Could be empty if the chunk is the last chunk of the text node. * `lastInTextNode` boolean read-only * Specifies whether the chunk is the last chunk of the text node. #### Methods * `` before(content ` Content `, contentOptions ` ContentOptions ` optional) `` : ` Element ` * Inserts content before the element. Content and ContentOptions Refer to [Global types](https://developers.cloudflare.com/workers/runtime-apis/html-rewriter/#global-types) for more information on `Content` and `ContentOptions`. * `` after(content ` Content `, contentOptions ` ContentOptions ` optional) `` : ` Element ` * Inserts content right after the element. * `` replace(content ` Content `, contentOptions ` ContentOptions ` optional) `` : ` Element ` * Removes the element and inserts content in place of it. * `remove()` : ` Element ` * Removes the element with all its content. ### Comments The `comments` function on an element handler allows developers to query and manipulate HTML comment tags. JavaScript ``` class ElementHandler { comments(comment) { // An incoming comment element, such as } } ``` #### Properties * `comment.removed` boolean * Indicates whether the element has been removed or replaced by one of the previous handlers. * `comment.text` string * The text of the comment. This property can be assigned different values, to modify comment's text. #### Methods * `` before(content ` Content `, contentOptions ` ContentOptions ` optional) `` : ` Element ` * Inserts content before the element. Content and ContentOptions Refer to [Global types](https://developers.cloudflare.com/workers/runtime-apis/html-rewriter/#global-types) for more information on `Content` and `ContentOptions`. * `` after(content ` Content `, contentOptions ` ContentOptions ` optional) `` : ` Element ` * Inserts content right after the element. * `` replace(content ` Content `, contentOptions ` ContentOptions ` optional) `` : ` Element ` * Removes the element and inserts content in place of it. * `remove()` : ` Element ` * Removes the element with all its content. ### Doctype The `doctype` function on a document handler allows developers to query a document's [doctype ↗](https://developer.mozilla.org/en-US/docs/Glossary/Doctype). JavaScript ``` class DocumentHandler { doctype(doctype) { // An incoming doctype element, such as // } } ``` #### Properties * `doctype.name` string | null read-only * The doctype name. * `doctype.publicId` string | null read-only * The quoted string in the doctype after the PUBLIC atom. * `doctype.systemId` string | null read-only * The quoted string in the doctype after the SYSTEM atom or immediately after the `publicId`. ### End The `end` function on a document handler allows developers to append content to the end of a document. JavaScript ``` class DocumentHandler { end(end) { // The end of the document } } ``` #### Methods * `` append(content ` Content `, contentOptions ` ContentOptions ` optional) `` : ` DocumentEnd ` * Inserts content after the end of the document. Content and ContentOptions Refer to [Global types](https://developers.cloudflare.com/workers/runtime-apis/html-rewriter/#global-types) for more information on `Content` and `ContentOptions`. --- ## Selectors This is what selectors are and what they are used for. * `*` * Any element. * `E` * Any element of type E. * `E:nth-child(n)` * An E element, the n-th child of its parent. * `E:first-child` * An E element, first child of its parent. * `E:nth-of-type(n)` * An E element, the n-th sibling of its type. * `E:first-of-type` * An E element, first sibling of its type. * `E:not(s)` * An E element that does not match either compound selectors. * `E.warning` * An E element belonging to the class warning. * `E#myid` * An E element with ID equal to myid. * `E[foo]` * An E element with a foo attribute. * `E[foo="bar"]` * An E element whose foo attribute value is exactly equal to bar. * `E[foo="bar" i]` * An E element whose foo attribute value is exactly equal to any (ASCII-range) case-permutation of bar. * `E[foo="bar" s]` * An E element whose foo attribute value is exactly and case-sensitively equal to bar. * `E[foo~="bar"]` * An E element whose foo attribute value is a list of whitespace-separated values, one of which is exactly equal to bar. * `E[foo^="bar"]` * An E element whose foo attribute value begins exactly with the string bar. * `E[foo$="bar"]` * An E element whose foo attribute value ends exactly with the string bar. * `E[foo*="bar"]` * An E element whose foo attribute value contains the substring bar. * `E[foo|="en"]` * An E element whose foo attribute value is a hyphen-separated list of values beginning with en. * `E F` * An F element descendant of an E element. * `E > F` * An F element child of an E element. --- ## Errors If a handler throws an exception, parsing is immediately halted, the transformed response body is errored with the thrown exception, and the untransformed response body is canceled (closed). If the transformed response body was already partially streamed back to the client, the client will see a truncated response. JavaScript ``` async function handle(request) { let oldResponse = await fetch(request); let newResponse = new HTMLRewriter() .on("*", { element(element) { throw new Error("A really bad error."); }, }) .transform(oldResponse); // At this point, an expression like `await newResponse.text()` // will throw `new Error("A really bad error.")`. // Thereafter, any use of `newResponse.body` will throw the same error, // and `oldResponse.body` will be closed. // Alternatively, this will produce a truncated response to the client: return newResponse; } ``` Explain Code --- ## Related resources * [Introducing HTMLRewriter ↗](https://blog.cloudflare.com/introducing-htmlrewriter/) * [Tutorial: Localize a Website](https://developers.cloudflare.com/pages/tutorials/localize-a-website/) * [Example: rewrite links](https://developers.cloudflare.com/workers/examples/rewrite-links/) * [Example: Inject Turnstile](https://developers.cloudflare.com/workers/examples/turnstile-html-rewriter/) * [Example: SPA shell with bootstrap data](https://developers.cloudflare.com/workers/examples/spa-shell/) ```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/runtime-apis/","name":"Runtime APIs"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/runtime-apis/html-rewriter/","name":"HTMLRewriter"}}]} ``` --- --- title: MessageChannel description: Channel messaging with MessageChannel and MessagePort image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/runtime-apis/messagechannel.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # MessageChannel ## Background The [MessageChannel API ↗](https://developer.mozilla.org/en-US/docs/Web/API/MessageChannel) provides a way to create a communication channel between different parts of your application. The Workers runtime provides a minimal implementation of the `MessageChannel` API that is currently limited to uses with a single Worker instance. This means that you can use `MessageChannel` to send messages between different parts of your Worker, but not across different Workers. JavaScript ``` const { port1, port2 } = new MessageChannel(); port2.onmessage = (event) => { console.log('Received message:', event.data); }; port2.postMessage('Hello from port2!'); ``` Any value that can be used with the `structuredClone(...)` API can be sent over the port. ## Differences There are a number of key limitations to the `MessageChannel` API in Workers: * Transfer lists are currently not supported. This means that you will not be able to transfer ownership of objects like `ArrayBuffer` or `MessagePort` between ports. * The `MessagePort` is not yet serializable. This means that you cannot send a `MessagePort` object through the `postMessage` method or via JSRPC calls. * The `'messageerror'` event is only partially supported. If the `'onmessage'` handler throws an error, the `'messageerror'` event will be triggered, however, it will not be triggered when there are errors serializing or deserializing the message data. Instead, the error will be thrown when the `postMessage` method is called on the sending port. * The `'close'` event will be emitted on both ports when one of the ports is closed, however it will not be emitted when the Worker is terminated or when one of the ports is garbage collected. ```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/runtime-apis/","name":"Runtime APIs"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/runtime-apis/messagechannel/","name":"MessageChannel"}}]} ``` --- --- title: Node.js compatibility description: Node.js APIs available in Cloudflare Workers image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/runtime-apis/nodejs/index.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Node.js compatibility When you write a Worker, you may need to import packages from [npm ↗](https://www.npmjs.com/). Many npm packages rely on APIs from the [Node.js runtime ↗](https://nodejs.org/en/about), and will not work unless these Node.js APIs are available. Cloudflare Workers provides a subset of Node.js APIs in two forms: 1. As built-in APIs provided by the Workers Runtime. Most of these APIs are full implementations of the corresponding Node.js APIs, while a few are partially supported or non-functional stubs intended for the APIs to be available for import only but not for actual use. 2. As polyfill shim implementations that [Wrangler](https://developers.cloudflare.com/workers/wrangler/) adds to your Worker's code, allowing it to import the module, but calling API methods will throw errors. ## Get Started To enable built-in Node.js APIs and add polyfills, add the `nodejs_compat` compatibility flag to your [Wrangler configuration file](https://developers.cloudflare.com/workers/wrangler/configuration/), and ensure that your Worker's [compatibility date](https://developers.cloudflare.com/workers/configuration/compatibility-dates/) is 2024-09-23 or later. [Learn more about the Node.js compatibility flag and v2](https://developers.cloudflare.com/workers/configuration/compatibility-flags/#nodejs-compatibility-flag). * [ wrangler.jsonc ](#tab-panel-7762) * [ wrangler.toml ](#tab-panel-7763) JSONC ``` { "compatibility_flags": [ "nodejs_compat" ], // Set this to today's date "compatibility_date": "2026-04-14" } ``` TOML ``` compatibility_flags = [ "nodejs_compat" ] # Set this to today's date compatibility_date = "2026-04-14" ``` ## Supported Node.js APIs The runtime APIs from Node.js listed below as "🟢 supported" are currently natively supported in the Workers Runtime. Item listed as "🟡 partially supported" are either only partially implemented or are implemented as non-functional stubs. [Deprecated or experimental APIs from Node.js ↗](https://nodejs.org/docs/latest/api/documentation.html#stability-index), and APIs that do not fit in a serverless context, are not included as part of the list below: | API Name | Natively supported by the Workers Runtime | | ----------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | | [Assertion testing](https://developers.cloudflare.com/workers/runtime-apis/nodejs/assert/) | 🟢 supported | | [Asynchronous context tracking](https://developers.cloudflare.com/workers/runtime-apis/nodejs/asynclocalstorage/) | 🟢 supported | | [Async hooks ↗](https://nodejs.org/docs/latest/api/async%5Fhooks.html) | 🟡 partially supported (non-functional) | | [Buffer](https://developers.cloudflare.com/workers/runtime-apis/nodejs/buffer/) | 🟢 supported | | [Child processes ↗](https://nodejs.org/docs/latest/api/child%5Fprocess.html) | 🟡 partially supported (non-functional) | | [Cluster ↗](https://nodejs.org/docs/latest/api/cluster.html) | 🟡 partially supported (non-functional) | | [Console ↗](https://nodejs.org/docs/latest/api/console.html) | 🟡 partially supported | | [Crypto](https://developers.cloudflare.com/workers/runtime-apis/nodejs/crypto/) | 🟢 supported | | [Debugger](https://developers.cloudflare.com/workers/observability/dev-tools/) | 🟢 supported via [Chrome Dev Tools integration](https://developers.cloudflare.com/workers/observability/dev-tools/) | | [Diagnostics Channel](https://developers.cloudflare.com/workers/runtime-apis/nodejs/diagnostics-channel/) | 🟢 supported | | [DNS](https://developers.cloudflare.com/workers/runtime-apis/nodejs/dns/) | 🟢 supported | | Errors | 🟢 supported | | [Events](https://developers.cloudflare.com/workers/runtime-apis/nodejs/eventemitter/) | 🟢 supported | | [File system](https://developers.cloudflare.com/workers/runtime-apis/nodejs/fs/) | 🟢 supported | | Globals | 🟢 supported | | [HTTP](https://developers.cloudflare.com/workers/runtime-apis/nodejs/http/) | 🟢 supported | | [HTTP/2 ↗](https://nodejs.org/docs/latest/api/http2.html) | 🟡 partially supported (non-functional) | | [HTTPS](https://developers.cloudflare.com/workers/runtime-apis/nodejs/https/) | 🟢 supported | | [Inspector ↗](https://nodejs.org/docs/latest/api/inspector.html) | 🟡 partially supported via [Chrome Dev Tools integration](https://developers.cloudflare.com/workers/observability/dev-tools/) | | [Module ↗](https://nodejs.org/docs/latest/api/module.html) | 🟡 partially supported | | [Net](https://developers.cloudflare.com/workers/runtime-apis/nodejs/net/) | 🟢 supported | | [OS ↗](https://nodejs.org/docs/latest/api/os.html) | 🟡 partially supported | | [Path](https://developers.cloudflare.com/workers/runtime-apis/nodejs/path/) | 🟢 supported | | [Performance hooks ↗](https://nodejs.org/docs/latest/api/perf%5Fhooks.html) | 🟡 partially supported | | [Process](https://developers.cloudflare.com/workers/runtime-apis/nodejs/process/) | 🟢 supported | | [Punycode ↗](https://nodejs.org/docs/latest/api/punycode.html) (deprecated) | 🟢 supported | | [Readline ↗](https://nodejs.org/docs/latest/api/readline.html) | 🟡 partially supported (non-functional) | | [REPL ↗](https://nodejs.org/docs/latest/api/repl.html) | 🟡 partially supported (non-functional) | | [Query strings ↗](https://nodejs.org/docs/latest/api/querystring.html) | 🟢 supported | | [SQLite ↗](https://nodejs.org/docs/latest/api/sqlite.html) | ⚪ not yet supported | | [Stream](https://developers.cloudflare.com/workers/runtime-apis/nodejs/streams) | 🟢 supported | | [String decoder](https://developers.cloudflare.com/workers/runtime-apis/nodejs/string-decoder/) | 🟢 supported | | [Test runner ↗](https://nodejs.org/docs/latest/api/test.html) | ⚪ not supported | | [Timers](https://developers.cloudflare.com/workers/runtime-apis/nodejs/timers/) | 🟢 supported | | [TLS/SSL](https://developers.cloudflare.com/workers/runtime-apis/nodejs/tls/) | 🟡 partially supported | | [UDP/datagram ↗](https://nodejs.org/docs/latest/api/dgram.html) | 🟡 partially supported (non-functional) | | [URL](https://developers.cloudflare.com/workers/runtime-apis/nodejs/url/) | 🟢 supported | | [Utilities](https://developers.cloudflare.com/workers/runtime-apis/nodejs/util/) | 🟢 supported | | [V8 ↗](https://nodejs.org/docs/latest/api/v8.html) | 🟡 partially supported (non-functional) | | [VM ↗](https://nodejs.org/docs/latest/api/vm.html) | 🟡 partially supported (non-functional) | | [Web Crypto API](https://developers.cloudflare.com/workers/runtime-apis/web-crypto/) | 🟢 supported | | [Web Streams API](https://developers.cloudflare.com/workers/runtime-apis/streams/) | 🟢 supported | | [Zlib](https://developers.cloudflare.com/workers/runtime-apis/nodejs/zlib/) | 🟢 supported | Unless otherwise specified, native implementations of Node.js APIs in Workers are intended to match the implementation in the [Current release of Node.js ↗](https://github.com/nodejs/release#release-schedule). If an API you wish to use is missing and you want to suggest that Workers support it, please add a post or comment in the[Node.js APIs discussions category ↗](https://github.com/cloudflare/workerd/discussions/categories/node-js-apis) on GitHub. ### Node.js API Polyfills Node.js APIs that are not yet supported in the Workers runtime are polyfilled via [Wrangler](https://developers.cloudflare.com/workers/wrangler/), which uses [unenv ↗](https://github.com/unjs/unenv). If the `nodejs_compat` [compatibility flag](https://developers.cloudflare.com/workers/configuration/compatibility-flags/) is enabled, and your Worker's [compatibility date](https://developers.cloudflare.com/workers/configuration/compatibility-dates/) is 2024-09-23 or later, Wrangler will automatically inject polyfills into your Worker's code. Adding polyfills maximizes compatibility with existing npm packages by providing modules with mocked methods. Calling these mocked methods will either noop or will throw an error with a message like: ``` [unenv] is not implemented yet! ``` This allows you to import packages that use these Node.js modules, even if certain methods are not supported. ## Enable only AsyncLocalStorage If you need to enable only the Node.js `AsyncLocalStorage` API, you can enable the `nodejs_als` compatibility flag: * [ wrangler.jsonc ](#tab-panel-7764) * [ wrangler.toml ](#tab-panel-7765) JSONC ``` { "compatibility_flags": [ "nodejs_als" ] } ``` TOML ``` compatibility_flags = [ "nodejs_als" ] ``` ```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/runtime-apis/","name":"Runtime APIs"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/runtime-apis/nodejs/","name":"Node.js compatibility"}}]} ``` --- --- title: assert description: The node:assert module in Node.js provides a number of useful assertions that are useful when building tests. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/runtime-apis/nodejs/assert.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # assert Note To enable built-in Node.js APIs and polyfills, add the nodejs\_compat compatibility flag to your [Wrangler configuration file](https://developers.cloudflare.com/workers/wrangler/configuration/). This also enables nodejs\_compat\_v2 as long as your compatibility date is 2024-09-23 or later. [Learn more about the Node.js compatibility flag and v2](https://developers.cloudflare.com/workers/configuration/compatibility-flags/#nodejs-compatibility-flag). The [node:assert ↗](https://nodejs.org/docs/latest/api/assert.html) module in Node.js provides a number of useful assertions that are useful when building tests. JavaScript ``` import { strictEqual, deepStrictEqual, ok, doesNotReject } from "node:assert"; strictEqual(1, 1); // ok! strictEqual(1, "1"); // fails! throws AssertionError deepStrictEqual({ a: { b: 1 } }, { a: { b: 1 } }); // ok! deepStrictEqual({ a: { b: 1 } }, { a: { b: 2 } }); // fails! throws AssertionError ok(true); // ok! ok(false); // fails! throws AssertionError await doesNotReject(async () => {}); // ok! await doesNotReject(async () => { throw new Error("boom"); }); // fails! throws AssertionError ``` Explain Code Note In the Workers implementation of `assert`, all assertions run in, what Node.js calls, the strict assertion mode. In strict assertion mode, non-strict methods behave like their corresponding strict methods. For example, `deepEqual()` will behave like `deepStrictEqual()`. Refer to the [Node.js documentation for assert ↗](https://nodejs.org/dist/latest-v19.x/docs/api/assert.html) for more information. ```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/runtime-apis/","name":"Runtime APIs"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/runtime-apis/nodejs/","name":"Node.js compatibility"}},{"@type":"ListItem","position":5,"item":{"@id":"/workers/runtime-apis/nodejs/assert/","name":"assert"}}]} ``` --- --- title: AsyncLocalStorage description: Cloudflare Workers provides an implementation of a subset of the Node.js AsyncLocalStorage API for creating in-memory stores that remain coherent through asynchronous operations. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/runtime-apis/nodejs/asynclocalstorage.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # AsyncLocalStorage ## Background Note To enable built-in Node.js APIs and polyfills, add the nodejs\_compat compatibility flag to your [Wrangler configuration file](https://developers.cloudflare.com/workers/wrangler/configuration/). This also enables nodejs\_compat\_v2 as long as your compatibility date is 2024-09-23 or later. [Learn more about the Node.js compatibility flag and v2](https://developers.cloudflare.com/workers/configuration/compatibility-flags/#nodejs-compatibility-flag). Cloudflare Workers provides an implementation of a subset of the Node.js [AsyncLocalStorage ↗](https://nodejs.org/dist/latest-v18.x/docs/api/async%5Fcontext.html#class-asynclocalstorage) API for creating in-memory stores that remain coherent through asynchronous operations. ## Constructor JavaScript ``` import { AsyncLocalStorage } from "node:async_hooks"; const asyncLocalStorage = new AsyncLocalStorage(); ``` * `new AsyncLocalStorage()` : AsyncLocalStorage * Returns a new `AsyncLocalStorage` instance. ## Methods * `getStore()` : any * Returns the current store. If called outside of an asynchronous context initialized by calling `asyncLocalStorage.run()`, it returns `undefined`. * `run(storeany, callbackfunction, ...argsarguments)` : any * Runs a function synchronously within a context and returns its return value. The store is not accessible outside of the callback function. The store is accessible to any asynchronous operations created within the callback. The optional `args` are passed to the callback function. If the callback function throws an error, the error is thrown by `run()` also. * `exit(callbackfunction, ...argsarguments)` : any * Runs a function synchronously outside of a context and returns its return value. This method is equivalent to calling `run()` with the `store` value set to `undefined`. ## Static Methods * `AsyncLocalStorage.bind(fn)` : function * Captures the asynchronous context that is current when `bind()` is called and returns a function that enters that context before calling the passed in function. * `AsyncLocalStorage.snapshot()` : function * Captures the asynchronous context that is current when `snapshot()` is called and returns a function that enters that context before calling a given function. ## Examples ### Fetch Listener JavaScript ``` import { AsyncLocalStorage } from 'node:async_hooks'; const asyncLocalStorage = new AsyncLocalStorage(); let idSeq = 0; export default { async fetch(req) { return asyncLocalStorage.run(idSeq++, () => { // Simulate some async activity... await scheduler.wait(1000); return new Response(asyncLocalStorage.getStore()); }); } }; ``` Explain Code ### Multiple stores The API supports multiple `AsyncLocalStorage` instances to be used concurrently. JavaScript ``` import { AsyncLocalStorage } from 'node:async_hooks'; const als1 = new AsyncLocalStorage(); const als2 = new AsyncLocalStorage(); export default { async fetch(req) { return als1.run(123, () => { return als2.run(321, () => { // Simulate some async activity... await scheduler.wait(1000); return new Response(`${als1.getStore()}-${als2.getStore()}`); }); }); } }; ``` Explain Code ### Unhandled Rejections When a `Promise` rejects and the rejection is unhandled, the async context propagates to the `'unhandledrejection'` event handler: JavaScript ``` import { AsyncLocalStorage } from "node:async_hooks"; const asyncLocalStorage = new AsyncLocalStorage(); let idSeq = 0; addEventListener("unhandledrejection", (event) => { console.log(asyncLocalStorage.getStore(), "unhandled rejection!"); }); export default { async fetch(req) { return asyncLocalStorage.run(idSeq++, () => { // Cause an unhandled rejection! throw new Error("boom"); }); }, }; ``` Explain Code ### `AsyncLocalStorage.bind()` and `AsyncLocalStorage.snapshot()` JavaScript ``` import { AsyncLocalStorage } from "node:async_hooks"; const als = new AsyncLocalStorage(); function foo() { console.log(als.getStore()); } function bar() { console.log(als.getStore()); } const oneFoo = als.run(123, () => AsyncLocalStorage.bind(foo)); oneFoo(); // prints 123 const snapshot = als.run("abc", () => AsyncLocalStorage.snapshot()); snapshot(foo); // prints 'abc' snapshot(bar); // prints 'abc' ``` Explain Code JavaScript ``` import { AsyncLocalStorage } from "node:async_hooks"; const als = new AsyncLocalStorage(); class MyResource { #runInAsyncScope = AsyncLocalStorage.snapshot(); doSomething() { this.#runInAsyncScope(() => { return als.getStore(); }); } } const myResource = als.run(123, () => new MyResource()); console.log(myResource.doSomething()); // prints 123 ``` Explain Code ## `AsyncResource` The [AsyncResource ↗](https://nodejs.org/dist/latest-v18.x/docs/api/async%5Fcontext.html#class-asyncresource) class is a component of Node.js' async context tracking API that allows users to create their own async contexts. Objects that extend from `AsyncResource` are capable of propagating the async context in much the same way as promises. Note that `AsyncLocalStorage.snapshot()` and `AsyncLocalStorage.bind()` provide a better approach. `AsyncResource` is provided solely for backwards compatibility with Node.js. ### Constructor JavaScript ``` import { AsyncResource, AsyncLocalStorage } from "node:async_hooks"; const als = new AsyncLocalStorage(); class MyResource extends AsyncResource { constructor() { // The type string is required by Node.js but unused in Workers. super("MyResource"); } doSomething() { this.runInAsyncScope(() => { return als.getStore(); }); } } const myResource = als.run(123, () => new MyResource()); console.log(myResource.doSomething()); // prints 123 ``` Explain Code * `new AsyncResource(typestring, optionsAsyncResourceOptions)` : AsyncResource * Returns a new `AsyncResource`. Importantly, while the constructor arguments are required in Node.js' implementation of `AsyncResource`, they are not used in Workers. * `AsyncResource.bind(fnfunction, typestring, thisArgany)` * Binds the given function to the current async context. ### Methods * `asyncResource.bind(fnfunction, thisArgany)` * Binds the given function to the async context associated with this `AsyncResource`. * `asyncResource.runInAsyncScope(fnfunction, thisArgany, ...argsarguments)` * Call the provided function with the given arguments in the async context associated with this `AsyncResource`. ## Caveats * The `AsyncLocalStorage` implementation provided by Workers intentionally omits support for the [asyncLocalStorage.enterWith() ↗](https://nodejs.org/dist/latest-v18.x/docs/api/async%5Fcontext.html#asynclocalstorageenterwithstore) and [asyncLocalStorage.disable() ↗](https://nodejs.org/dist/latest-v18.x/docs/api/async%5Fcontext.html#asynclocalstoragedisable) methods. * Workers does not implement the full [async\_hooks ↗](https://nodejs.org/dist/latest-v18.x/docs/api/async%5Fhooks.html) API upon which Node.js' implementation of `AsyncLocalStorage` is built. * Workers does not implement the ability to create an `AsyncResource` with an explicitly identified trigger context as allowed by Node.js. This means that a new `AsyncResource` will always be bound to the async context in which it was created. * Thenables (non-Promise objects that expose a `then()` method) are not fully supported when using `AsyncLocalStorage`. When working with thenables, instead use [AsyncLocalStorage.snapshot() ↗](https://nodejs.org/api/async%5Fcontext.html#static-method-asynclocalstoragesnapshot) to capture a snapshot of the current context. ```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/runtime-apis/","name":"Runtime APIs"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/runtime-apis/nodejs/","name":"Node.js compatibility"}},{"@type":"ListItem","position":5,"item":{"@id":"/workers/runtime-apis/nodejs/asynclocalstorage/","name":"AsyncLocalStorage"}}]} ``` --- --- title: Buffer description: The Buffer API in Node.js is one of the most commonly used Node.js APIs for manipulating binary data. Every Buffer instance extends from the standard Uint8Array class, but adds a range of unique capabilities such as built-in base64 and hex encoding/decoding, byte-order manipulation, and encoding-aware substring searching. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/runtime-apis/nodejs/buffer.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Buffer Note To enable built-in Node.js APIs and polyfills, add the nodejs\_compat compatibility flag to your [Wrangler configuration file](https://developers.cloudflare.com/workers/wrangler/configuration/). This also enables nodejs\_compat\_v2 as long as your compatibility date is 2024-09-23 or later. [Learn more about the Node.js compatibility flag and v2](https://developers.cloudflare.com/workers/configuration/compatibility-flags/#nodejs-compatibility-flag). The [Buffer ↗](https://nodejs.org/docs/latest/api/buffer.html) API in Node.js is one of the most commonly used Node.js APIs for manipulating binary data. Every `Buffer` instance extends from the standard [Uint8Array ↗](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global%5FObjects/Uint8Array) class, but adds a range of unique capabilities such as built-in base64 and hex encoding/decoding, byte-order manipulation, and encoding-aware substring searching. JavaScript ``` import { Buffer } from "node:buffer"; const buf = Buffer.from("hello world", "utf8"); console.log(buf.toString("hex")); // Prints: 68656c6c6f20776f726c64 console.log(buf.toString("base64")); // Prints: aGVsbG8gd29ybGQ= ``` A Buffer extends from `Uint8Array`. Therefore, it can be used in any Workers API that currently accepts `Uint8Array`, such as creating a new Response: JavaScript ``` const response = new Response(Buffer.from("hello world")); ``` You can also use the `Buffer` API when interacting with streams: JavaScript ``` const writable = getWritableStreamSomehow(); const writer = writable.getWriter(); writer.write(Buffer.from("hello world")); ``` One key difference between the Workers implementation of `Buffer` and the Node.js implementation is that some methods of creating a `Buffer` in Node.js will allocate those from a global memory pool as a performance optimization. The Workers implementation does not use a memory pool and all `Buffer` instances are allocated independently. Further, in Node.js it is possible to allocate a `Buffer` with uninitialized memory using the `Buffer.allocUnsafe()` method. This is not supported in Workers and `Buffer`instances are always initialized so that the `Buffer` is always filled with null bytes (`0x00`) when allocated. Refer to the [Node.js documentation for Buffer ↗](https://nodejs.org/dist/latest-v19.x/docs/api/buffer.html) for more information. ```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/runtime-apis/","name":"Runtime APIs"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/runtime-apis/nodejs/","name":"Node.js compatibility"}},{"@type":"ListItem","position":5,"item":{"@id":"/workers/runtime-apis/nodejs/buffer/","name":"Buffer"}}]} ``` --- --- title: crypto description: The node:crypto module provides cryptographic functionality that includes a set of wrappers for OpenSSL's hash, HMAC, cipher, decipher, sign, and verify functions. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/runtime-apis/nodejs/crypto.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # crypto Note To enable built-in Node.js APIs and polyfills, add the nodejs\_compat compatibility flag to your [Wrangler configuration file](https://developers.cloudflare.com/workers/wrangler/configuration/). This also enables nodejs\_compat\_v2 as long as your compatibility date is 2024-09-23 or later. [Learn more about the Node.js compatibility flag and v2](https://developers.cloudflare.com/workers/configuration/compatibility-flags/#nodejs-compatibility-flag). The [node:crypto ↗](https://nodejs.org/docs/latest/api/crypto.html) module provides cryptographic functionality that includes a set of wrappers for OpenSSL's hash, HMAC, cipher, decipher, sign, and verify functions. All `node:crypto` APIs are fully supported in Workers with the following exceptions: * The functions [generateKeyPair ↗](https://nodejs.org/api/crypto.html#cryptogeneratekeypairtype-options-callback) and [generateKeyPairSync ↗](https://nodejs.org/api/crypto.html#cryptogeneratekeypairsynctype-options)do not support DSA or DH key pairs. * `ed448` and `x448` curves are not supported. * It is not possible to manually enable or disable [FIPS mode ↗](https://nodejs.org/docs/latest/api/crypto.html#fips-mode). The full `node:crypto` API is documented in the [Node.js documentation for node:crypto ↗](https://nodejs.org/api/crypto.html). The [WebCrypto API](https://developers.cloudflare.com/workers/runtime-apis/web-crypto/) is also available within Cloudflare Workers. This does not require the `nodejs_compat` compatibility flag. ```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/runtime-apis/","name":"Runtime APIs"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/runtime-apis/nodejs/","name":"Node.js compatibility"}},{"@type":"ListItem","position":5,"item":{"@id":"/workers/runtime-apis/nodejs/crypto/","name":"crypto"}}]} ``` --- --- title: Diagnostics Channel description: The diagnostics_channel module provides an API to create named channels to report arbitrary message data for diagnostics purposes. The API is essentially a simple event pub/sub model that is specifically designed to support low-overhead diagnostics reporting. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/runtime-apis/nodejs/diagnostics-channel.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Diagnostics Channel Note To enable built-in Node.js APIs and polyfills, add the nodejs\_compat compatibility flag to your [Wrangler configuration file](https://developers.cloudflare.com/workers/wrangler/configuration/). This also enables nodejs\_compat\_v2 as long as your compatibility date is 2024-09-23 or later. [Learn more about the Node.js compatibility flag and v2](https://developers.cloudflare.com/workers/configuration/compatibility-flags/#nodejs-compatibility-flag). The [diagnostics\_channel ↗](https://nodejs.org/dist/latest-v20.x/docs/api/diagnostics%5Fchannel.html) module provides an API to create named channels to report arbitrary message data for diagnostics purposes. The API is essentially a simple event pub/sub model that is specifically designed to support low-overhead diagnostics reporting. JavaScript ``` import { channel, hasSubscribers, subscribe, unsubscribe, tracingChannel, } from "node:diagnostics_channel"; // For publishing messages to a channel, acquire a channel object: const myChannel = channel("my-channel"); // Any JS value can be published to a channel. myChannel.publish({ foo: "bar" }); // For receiving messages on a channel, use subscribe: subscribe("my-channel", (message) => { console.log(message); }); ``` Explain Code All `Channel` instances are singletons per each Isolate/context (for example, the same entry point). Subscribers are always invoked synchronously and in the order they were registered, much like an `EventTarget` or Node.js `EventEmitter` class. ## Integration with Tail Workers When using [Tail Workers](https://developers.cloudflare.com/workers/observability/logs/tail-workers/), all messages published to any channel will be forwarded also to the [Tail Worker](https://developers.cloudflare.com/workers/observability/logs/tail-workers/). Within the Tail Worker, the diagnostic channel messages can be accessed via the `diagnosticsChannelEvents` property: JavaScript ``` export default { async tail(events) { for (const event of events) { for (const messageData of event.diagnosticsChannelEvents) { console.log( messageData.timestamp, messageData.channel, messageData.message, ); } } }, }; ``` Explain Code Note that message published to the tail worker is passed through the [structured clone algorithm ↗](https://developer.mozilla.org/en-US/docs/Web/API/Web%5FWorkers%5FAPI/Structured%5Fclone%5Falgorithm) (same mechanism as the [structuredClone() ↗](https://developer.mozilla.org/en-US/docs/Web/API/structuredClone) API) so only values that can be successfully cloned are supported. ## `TracingChannel` Per the Node.js documentation, "[TracingChannel ↗](https://nodejs.org/api/diagnostics%5Fchannel.html#class-tracingchannel) is a collection of \[Channels\] which together express a single traceable action. `TracingChannel` is used to formalize and simplify the process of producing events for tracing application flow." JavaScript ``` import { tracingChannel } from "node:diagnostics_channel"; import { AsyncLocalStorage } from "node:async_hooks"; const channels = tracingChannel("my-channel"); const requestId = new AsyncLocalStorage(); channels.start.bindStore(requestId); channels.subscribe({ start(message) { console.log(requestId.getStore()); // { requestId: '123' } // Handle start message }, end(message) { console.log(requestId.getStore()); // { requestId: '123' } // Handle end message }, asyncStart(message) { console.log(requestId.getStore()); // { requestId: '123' } // Handle asyncStart message }, asyncEnd(message) { console.log(requestId.getStore()); // { requestId: '123' } // Handle asyncEnd message }, error(message) { console.log(requestId.getStore()); // { requestId: '123' } // Handle error message }, }); // The subscriber handlers will be invoked while tracing the execution of the async // function passed into `channel.tracePromise`... channel.tracePromise( async () => { // Perform some asynchronous work... }, { requestId: "123" }, ); ``` Explain Code Refer to the [Node.js documentation for diagnostics\_channel ↗](https://nodejs.org/dist/latest-v20.x/docs/api/diagnostics%5Fchannel.html) for more information. ```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/runtime-apis/","name":"Runtime APIs"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/runtime-apis/nodejs/","name":"Node.js compatibility"}},{"@type":"ListItem","position":5,"item":{"@id":"/workers/runtime-apis/nodejs/diagnostics-channel/","name":"Diagnostics Channel"}}]} ``` --- --- title: dns description: You can use node:dns for name resolution via DNS over HTTPS using Cloudflare DNS at 1.1.1.1. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/runtime-apis/nodejs/dns.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # dns Note To enable built-in Node.js APIs and polyfills, add the nodejs\_compat compatibility flag to your [Wrangler configuration file](https://developers.cloudflare.com/workers/wrangler/configuration/). This also enables nodejs\_compat\_v2 as long as your compatibility date is 2024-09-23 or later. [Learn more about the Node.js compatibility flag and v2](https://developers.cloudflare.com/workers/configuration/compatibility-flags/#nodejs-compatibility-flag). You can use [node:dns ↗](https://nodejs.org/api/dns.html) for name resolution via [DNS over HTTPS](https://developers.cloudflare.com/1.1.1.1/encryption/dns-over-https/) using[Cloudflare DNS ↗](https://www.cloudflare.com/application-services/products/dns/) at 1.1.1.1. * [ JavaScript ](#tab-panel-7766) * [ TypeScript ](#tab-panel-7767) index.js ``` import dns from "node:dns"; let response = await dns.promises.resolve4("cloudflare.com", "NS"); ``` index.ts ``` import dns from 'node:dns'; let response = await dns.promises.resolve4('cloudflare.com', 'NS'); ``` All `node:dns` functions are available, except `lookup`, `lookupService`, and `resolve` which throw "Not implemented" errors when called. Note DNS requests will execute a subrequest, counts for your [Worker's subrequest limit](https://developers.cloudflare.com/workers/platform/limits/#subrequests). The full `node:dns` API is documented in the [Node.js documentation for node:dns ↗](https://nodejs.org/api/dns.html). ``` ``` ```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/runtime-apis/","name":"Runtime APIs"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/runtime-apis/nodejs/","name":"Node.js compatibility"}},{"@type":"ListItem","position":5,"item":{"@id":"/workers/runtime-apis/nodejs/dns/","name":"dns"}}]} ``` --- --- title: EventEmitter description: An EventEmitter is an object that emits named events that cause listeners to be called. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/runtime-apis/nodejs/EventEmitter.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # EventEmitter Note To enable built-in Node.js APIs and polyfills, add the nodejs\_compat compatibility flag to your [Wrangler configuration file](https://developers.cloudflare.com/workers/wrangler/configuration/). This also enables nodejs\_compat\_v2 as long as your compatibility date is 2024-09-23 or later. [Learn more about the Node.js compatibility flag and v2](https://developers.cloudflare.com/workers/configuration/compatibility-flags/#nodejs-compatibility-flag). An [EventEmitter ↗](https://nodejs.org/docs/latest/api/events.html#class-eventemitter)is an object that emits named events that cause listeners to be called. JavaScript ``` import { EventEmitter } from "node:events"; const emitter = new EventEmitter(); emitter.on("hello", (...args) => { console.log(...args); // 1 2 3 }); emitter.emit("hello", 1, 2, 3); ``` The implementation in the Workers runtime supports the entire Node.js `EventEmitter` API. This includes the [captureRejections ↗](https://nodejs.org/docs/latest/api/events.html#capture-rejections-of-promises)option that allows improved handling of async functions as event handlers: JavaScript ``` const emitter = new EventEmitter({ captureRejections: true }); emitter.on("hello", async (...args) => { throw new Error("boom"); }); emitter.on("error", (err) => { // the async promise rejection is emitted here! }); ``` Like Node.js, when an `'error'` event is emitted on an `EventEmitter` and there is no listener for it, the error will be immediately thrown. However, in Node.js it is possible to add a handler on the `process` object for the`'uncaughtException'` event to catch globally uncaught exceptions. The`'uncaughtException'` event, however, is currently not implemented in the Workers runtime. It is strongly recommended to always add an `'error'` listener to any `EventEmitter` instance. Refer to the [Node.js documentation for EventEmitter ↗](https://nodejs.org/api/events.html#class-eventemitter) for more information. ```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/runtime-apis/","name":"Runtime APIs"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/runtime-apis/nodejs/","name":"Node.js compatibility"}},{"@type":"ListItem","position":5,"item":{"@id":"/workers/runtime-apis/nodejs/eventemitter/","name":"EventEmitter"}}]} ``` --- --- title: fs description: You can use node:fs to access a virtual file system in Workers. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/runtime-apis/nodejs/fs.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # fs Note To enable built-in Node.js APIs and polyfills, add the nodejs\_compat compatibility flag to your [Wrangler configuration file](https://developers.cloudflare.com/workers/wrangler/configuration/). This also enables nodejs\_compat\_v2 as long as your compatibility date is 2024-09-23 or later. [Learn more about the Node.js compatibility flag and v2](https://developers.cloudflare.com/workers/configuration/compatibility-flags/#nodejs-compatibility-flag). You can use [node:fs ↗](https://nodejs.org/api/fs.html) to access a virtual file system in Workers. The `node:fs` module is available in Workers runtimes that support Node.js compatibility using the `nodejs_compat` compatibility flag. Any Worker running with `nodejs_compat` enabled and with a compatibility date of`2025-09-01` or later will have access to `node:fs` by default. It is also possible to enable `node:fs` on Workers with an earlier compatibility date using a combination of the `nodejs_compat` and `enable_nodejs_fs_module`flags. To disable `node:fs` you can set the `disable_nodejs_fs_module` flag. JavaScript ``` import { readFileSync, writeFileSync } from "node:fs"; const config = readFileSync("/bundle/config.txt", "utf8"); writeFileSync("/tmp/abc.txt", "Hello, world!"); ``` The Workers Virtual File System (VFS) is a memory-based file system that allows you to read modules included in your Worker bundle as read-only files, access a directory for writing temporary files, or access common[character devices ↗](https://linux-kernel-labs.github.io/refs/heads/master/labs/device%5Fdrivers.html) like`/dev/null`, `/dev/random`, `/dev/full`, and `/dev/zero`. The directory structure initially looks like: ``` /bundle └── (one file for each module in your Worker bundle) /tmp └── (empty, but you can write files, create directories, symlinks, etc) /dev ├── null ├── random ├── full └── zero ``` The `/bundle` directory contains the files for all modules included in your Worker bundle, which you can read using APIs like `readFileSync` or`read(...)`, etc. These are always read-only. Reading from the bundle can be useful when you need to read a config file or a template. JavaScript ``` import { readFileSync } from "node:fs"; // The config.txt file would be included in your Worker bundle. // Refer to the Wrangler documentation for details on how to // include additional files. const config = readFileSync("/bundle/config.txt", "utf8"); export default { async fetch(request) { return new Response(`Config contents: ${config}`); }, }; ``` Explain Code The `/tmp` directory is writable, and you can use it to create temporary files or directories. You can also create symlinks in this directory. However, the contents of `/tmp` are not persistent and are unique to each request. This means that files created in `/tmp` within the context of one request will not be available in other concurrent or subsequent requests. JavaScript ``` import { writeFileSync, readFileSync } from "node:fs"; export default { fetch(request) { // The file `/tmp/hello.txt` will only exist for the duration // of this request. writeFileSync("/tmp/hello.txt", "Hello, world!"); const contents = readFileSync("/tmp/hello.txt", "utf8"); return new Response(`File contents: ${contents}`); }, }; ``` Explain Code The `/dev` directory contains common character devices: * `/dev/null`: A null device that discards all data written to it and returns EOF on read. * `/dev/random`: A device that provides random bytes on reads and discards all data written to it. Reading from `/dev/random` is only permitted when within the context of a request. * `/dev/full`: A device that always returns EOF on reads and discards all data written to it. * `/dev/zero`: A device that provides an infinite stream of zero bytes on reads and discards all data written to it. All operations on the VFS are synchronous. You can use the synchronous, asynchronous callback, or promise-based APIs provided by the `node:fs` module but all operations will be performed synchronously. Timestamps for files in the VFS are currently always set to the Unix epoch (`1970-01-01T00:00:00Z`). This means that operations that rely on timestamps, like `fs.stat`, will always return the same timestamp for all files in the VFS. This is a temporary limitation that will be addressed in a future release. Since all temporary files are held in memory, the total size of all temporary files and directories created count towards your Worker’s memory limit. If you exceed this limit, the Worker instance will be terminated and restarted. The file system implementation has the following limits: * The maximum total length of a file path is 4096 characters, including path separators. Because paths are handled as file URLs internally, the limit accounts for percent-encoding of special characters, decoding characters that do not need encoding before the limit is checked. For example, the path `/tmp/abcde%66/ghi%zz' is 18 characters long because the `%66`does not need to be percent-encoded and is therefore counted as one character, while the`%zz\` is an invalid percent-encoding that is counted as 3 characters. * The maximum number of path segments is 48\. For example, the path `/a/b/c` is 3 segments. * The maximum size of an individual file is 128 MB total. The following `node:fs` APIs are not supported in Workers, or are only partially supported: * `fs.watch` and `fs.watchFile` operations for watching for file changes. * The `fs.globSync()` and other glob APIs have not yet been implemented. * The `force` option in the `fs.rm` API has not yet been implemented. * Timestamps for files are always set to the Unix epoch (`1970-01-01T00:00:00Z`). * File permissions and ownership are not supported. The full `node:fs` API is documented in the [Node.js documentation for node:fs ↗](https://nodejs.org/api/fs.html). ```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/runtime-apis/","name":"Runtime APIs"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/runtime-apis/nodejs/","name":"Node.js compatibility"}},{"@type":"ListItem","position":5,"item":{"@id":"/workers/runtime-apis/nodejs/fs/","name":"fs"}}]} ``` --- --- title: http description: To use the HTTP client-side methods (http.get, http.request, etc.), you must enable the enable_nodejs_http_modules compatibility flag in addition to the nodejs_compat flag. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/runtime-apis/nodejs/http.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # http Note To enable built-in Node.js APIs and polyfills, add the nodejs\_compat compatibility flag to your [Wrangler configuration file](https://developers.cloudflare.com/workers/wrangler/configuration/). This also enables nodejs\_compat\_v2 as long as your compatibility date is 2024-09-23 or later. [Learn more about the Node.js compatibility flag and v2](https://developers.cloudflare.com/workers/configuration/compatibility-flags/#nodejs-compatibility-flag). ## Compatibility flags ### Client-side methods To use the HTTP client-side methods (`http.get`, `http.request`, etc.), you must enable the [enable\_nodejs\_http\_modules](https://developers.cloudflare.com/workers/configuration/compatibility-flags/) compatibility flag in addition to the [nodejs\_compat](https://developers.cloudflare.com/workers/runtime-apis/nodejs/) flag. This flag is automatically enabled for Workers using a [compatibility date](https://developers.cloudflare.com/workers/configuration/compatibility-dates/) of `2025-08-15` or later when `nodejs_compat` is enabled. For Workers using an earlier compatibility date, you can manually enable it by adding the flag to your Wrangler configuration file: * [ wrangler.jsonc ](#tab-panel-7768) * [ wrangler.toml ](#tab-panel-7769) JSONC ``` { "compatibility_flags": [ "nodejs_compat", "enable_nodejs_http_modules" ] } ``` TOML ``` compatibility_flags = [ "nodejs_compat", "enable_nodejs_http_modules" ] ``` ### Server-side methods To use the HTTP server-side methods (`http.createServer`, `http.Server`, `http.ServerResponse`), you must enable the `enable_nodejs_http_server_modules` compatibility flag in addition to the [nodejs\_compat](https://developers.cloudflare.com/workers/runtime-apis/nodejs/) flag. This flag is automatically enabled for Workers using a [compatibility date](https://developers.cloudflare.com/workers/configuration/compatibility-dates/) of `2025-09-01` or later when `nodejs_compat` is enabled. For Workers using an earlier compatibility date, you can manually enable it by adding the flag to your Wrangler configuration file: * [ wrangler.jsonc ](#tab-panel-7770) * [ wrangler.toml ](#tab-panel-7771) JSONC ``` { "compatibility_flags": [ "nodejs_compat", "enable_nodejs_http_server_modules" ] } ``` TOML ``` compatibility_flags = [ "nodejs_compat", "enable_nodejs_http_server_modules" ] ``` To use both client-side and server-side methods, enable both flags: * [ wrangler.jsonc ](#tab-panel-7772) * [ wrangler.toml ](#tab-panel-7773) JSONC ``` { "compatibility_flags": [ "nodejs_compat", "enable_nodejs_http_modules", "enable_nodejs_http_server_modules" ] } ``` TOML ``` compatibility_flags = [ "nodejs_compat", "enable_nodejs_http_modules", "enable_nodejs_http_server_modules" ] ``` ## get An implementation of the Node.js [http.get ↗](https://nodejs.org/docs/latest/api/http.html#httpgetoptions-callback) method. The `get` method performs a GET request to the specified URL and invokes the callback with the response. It's a convenience method that simplifies making HTTP GET requests without manually configuring request options. Because `get` is a wrapper around `fetch(...)`, it may be used only within an exported fetch or similar handler. Outside of such a handler, attempts to use `get` will throw an error. JavaScript ``` import { get } from "node:http"; export default { async fetch() { const { promise, resolve, reject } = Promise.withResolvers(); get("http://example.org", (res) => { let data = ""; res.setEncoding("utf8"); res.on("data", (chunk) => { data += chunk; }); res.on("end", () => { resolve(new Response(data)); }); res.on("error", reject); }).on("error", reject); return promise; }, }; ``` Explain Code The implementation of `get` in Workers is a wrapper around the global[fetch API ↗](https://developers.cloudflare.com/workers/runtime-apis/fetch/)and is therefore subject to the same [limits ↗](https://developers.cloudflare.com/workers/platform/limits/). As shown in the example above, it is necessary to arrange for requests to be correctly awaited in the `fetch` handler using a promise or the fetch may be canceled prematurely when the handler returns. ## request An implementation of the Node.js [\`http.request' ↗](https://nodejs.org/docs/latest/api/http.html#httprequesturl-options-callback) method. The `request` method creates an HTTP request with customizable options like method, headers, and body. It provides full control over the request configuration and returns a Node.js [stream.Writable ↗](https://developers.cloudflare.com/workers/runtime-apis/nodejs/streams/) for sending request data. Because `request` is a wrapper around `fetch(...)`, it may be used only within an exported fetch or similar handler. Outside of such a handler, attempts to use `request` will throw an error. JavaScript ``` import { get } from "node:http"; export default { async fetch() { const { promise, resolve, reject } = Promise.withResolvers(); get( { method: "GET", protocol: "http:", hostname: "example.org", path: "/", }, (res) => { let data = ""; res.setEncoding("utf8"); res.on("data", (chunk) => { data += chunk; }); res.on("end", () => { resolve(new Response(data)); }); res.on("error", reject); }, ) .on("error", reject) .end(); return promise; }, }; ``` Explain Code The following options passed to the `request` (and `get`) method are not supported due to the differences required by Cloudflare Workers implementation of `node:http` as a wrapper around the global `fetch` API: * `maxHeaderSize` * `insecureHTTPParser` * `createConnection` * `lookup` * `socketPath` ## OutgoingMessage The [OutgoingMessage ↗](https://nodejs.org/docs/latest/api/http.html#class-httpoutgoingmessage) class represents an HTTP response that is sent to the client. It provides methods for writing response headers and body, as well as for ending the response. `OutgoingMessage` extends from the Node.js [stream.Writable stream class ↗](https://developers.cloudflare.com/workers/runtime-apis/nodejs/streams/). The `OutgoingMessage` class is a base class for outgoing HTTP messages (both requests and responses). It provides methods for writing headers and body data, as well as for ending the message. `OutgoingMessage` extends from the [Writable stream class ↗](https://nodejs.org/docs/latest/api/stream.html#class-streamwritable). Both `ClientRequest` and `ServerResponse` both extend from and inherit from `OutgoingMessage`. ## IncomingMessage The `IncomingMessage` class represents an HTTP request that is received from the client. It provides methods for reading request headers and body, as well as for ending the request. `IncomingMessage` extends from the `Readable` stream class. The `IncomingMessage` class represents an HTTP message (request or response). It provides methods for reading headers and body data. `IncomingMessage` extends from the `Readable` stream class. JavaScript ``` import { get, IncomingMessage } from "node:http"; import { ok, strictEqual } from "node:assert"; export default { async fetch() { // ... get("http://example.org", (res) => { ok(res instanceof IncomingMessage); }); // ... }, }; ``` Explain Code The Workers implementation includes a `cloudflare` property on `IncomingMessage` objects: JavaScript ``` import { createServer } from "node:http"; import { httpServerHandler } from "cloudflare:node"; const server = createServer((req, res) => { console.log(req.cloudflare.cf.country); console.log(req.cloudflare.cf.ray); res.write("Hello, World!"); res.end(); }); server.listen(8080); export default httpServerHandler({ port: 8080 }); ``` Explain Code The `cloudflare.cf` property contains [Cloudflare-specific request properties](https://developers.cloudflare.com/workers/runtime-apis/request/#incomingrequestcfproperties). The following differences exist between the Workers implementation and Node.js: * Trailer headers are not supported * The `socket` attribute **does not extend from `net.Socket`** and only contains the following properties: `encrypted`, `remoteFamily`, `remoteAddress`, `remotePort`, `localAddress`, `localPort`, and `destroy()` method. * The following `socket` attributes behave differently than their Node.js counterparts: * `remoteAddress` will return `127.0.0.1` when ran locally * `remotePort` will return a random port number between 2^15 and 2^16 * `localAddress` will return the value of request's `host` header if exists. Otherwise, it will return `127.0.0.1` * `localPort` will return the port number assigned to the server instance * `req.socket.destroy()` falls through to `req.destroy()` ## Agent A partial implementation of the Node.js [\`http.Agent' ↗](https://nodejs.org/docs/latest/api/http.html#class-httpagent) class. An `Agent` manages HTTP connection reuse by maintaining request queues per host/port. In the workers environment, however, such low-level management of the network connection, ports, etc, is not relevant because it is handled by the Cloudflare infrastructure instead. Accordingly, the implementation of `Agent` in Workers is a stub implementation that does not support connection pooling or keep-alive. JavaScript ``` import { Agent } from "node:http"; import { strictEqual } from "node:assert"; const agent = new Agent(); strictEqual(agent.protocol, "http:"); ``` ## createServer An implementation of the Node.js [http.createServer ↗](https://nodejs.org/docs/latest/api/http.html#httpcreateserveroptions-requestlistener) method. The `createServer` method creates an HTTP server instance that can handle incoming requests. JavaScript ``` import { createServer } from "node:http"; import { httpServerHandler } from "cloudflare:node"; const server = createServer((req, res) => { res.writeHead(200, { "Content-Type": "text/plain" }); res.end("Hello from Node.js HTTP server!"); }); server.listen(8080); export default httpServerHandler({ port: 8080 }); ``` Explain Code ## Node.js integration ### httpServerHandler The `httpServerHandler` function integrates Node.js HTTP servers with the Cloudflare Workers request model. It supports two API patterns: JavaScript ``` import http from "node:http"; import { httpServerHandler } from "cloudflare:node"; const server = http.createServer((req, res) => { res.end("hello world"); }); // Pass server directly (simplified) - automatically calls listen() if needed export default httpServerHandler(server); // Or use port-based routing for multiple servers server.listen(8080); export default httpServerHandler({ port: 8080 }); ``` Explain Code The handler automatically routes incoming Worker requests to your Node.js server. When using port-based routing, the port number acts as a routing key to determine which server handles requests, allowing multiple servers to coexist in the same Worker. ### handleAsNodeRequest For more direct control over request routing, you can use the `handleAsNodeRequest` function from `cloudflare:node`. This function directly routes a Worker request to a Node.js server running on a specific port: JavaScript ``` import { createServer } from "node:http"; import { handleAsNodeRequest } from "cloudflare:node"; const server = createServer((req, res) => { res.writeHead(200, { "Content-Type": "text/plain" }); res.end("Hello from Node.js HTTP server!"); }); server.listen(8080); export default { fetch(request) { return handleAsNodeRequest(8080, request); }, }; ``` Explain Code This approach gives you full control over the fetch handler while still leveraging Node.js HTTP servers for request processing. Note Failing to call `close()` on an HTTP server may result in the server persisting until the worker is destroyed. In most cases, this is not an issue since servers typically live for the lifetime of the worker. However, if you need to create multiple servers during a worker's lifetime or want explicit lifecycle control (such as in test scenarios), call `close()` when you're done with the server, or use [explicit resource management ↗](https://v8.dev/features/explicit-resource-management). ## Server An implementation of the Node.js [http.Server ↗](https://nodejs.org/docs/latest/api/http.html#class-httpserver) class. The `Server` class represents an HTTP server and provides methods for handling incoming requests. It extends the Node.js `EventEmitter` class and can be used to create custom server implementations. When using `httpServerHandler`, the port number specified in `server.listen()` acts as a routing key rather than an actual network port. The handler uses this port to determine which HTTP server instance should handle incoming requests, allowing multiple servers to coexist within the same Worker by using different port numbers for identification. Using a port value of `0` (or `null` or `undefined`) will result in a random port number being assigned. JavaScript ``` import { Server } from "node:http"; import { httpServerHandler } from "cloudflare:node"; const server = new Server((req, res) => { res.writeHead(200, { "Content-Type": "application/json" }); res.end(JSON.stringify({ message: "Hello from HTTP Server!" })); }); server.listen(8080); export default httpServerHandler({ port: 8080 }); ``` Explain Code The following differences exist between the Workers implementation and Node.js: * Connection management methods such as `closeAllConnections()` and `closeIdleConnections()` are not implemented * Only `listen()` variants with a port number or no parameters are supported: `listen()`, `listen(0, callback)`, `listen(callback)`, etc. For reference, see the [Node.js documentation ↗](https://nodejs.org/docs/latest/api/net.html#serverlisten). * The following server options are not supported: `maxHeaderSize`, `insecureHTTPParser`, `keepAliveTimeout`, `connectionsCheckingInterval` ## ServerResponse An implementation of the Node.js [http.ServerResponse ↗](https://nodejs.org/docs/latest/api/http.html#class-httpserverresponse) class. The `ServerResponse` class represents the server-side response object that is passed to request handlers. It provides methods for writing response headers and body data, and extends the Node.js `Writable` stream class. JavaScript ``` import { createServer, ServerResponse } from "node:http"; import { httpServerHandler } from "cloudflare:node"; import { ok } from "node:assert"; const server = createServer((req, res) => { ok(res instanceof ServerResponse); // Set multiple headers at once res.writeHead(200, { "Content-Type": "application/json", "X-Custom-Header": "Workers-HTTP", }); // Stream response data res.write('{"data": ['); res.write('{"id": 1, "name": "Item 1"},'); res.write('{"id": 2, "name": "Item 2"}'); res.write("]}"); // End the response res.end(); }); export default httpServerHandler(server); ``` Explain Code The following methods and features are not supported in the Workers implementation: * `assignSocket()` and `detachSocket()` methods are not available * Trailer headers are not supported * `writeContinue()` and `writeEarlyHints()` methods are not available * 1xx responses in general are not supported ## Other differences between Node.js and Workers implementation of `node:http` Because the Workers implementation of `node:http` is a wrapper around the global `fetch` API, there are some differences in behavior and limitations compared to a standard Node.js environment: * `Connection` headers are not used. Workers will manage connections automatically. * `Content-Length` headers will be handled the same way as in the `fetch` API. If a body is provided, the header will be set automatically and manually set values will be ignored. * `Expect: 100-continue` headers are not supported. * Trailing headers are not supported. * The `'continue'` event is not supported. * The `'information'` event is not supported. * The `'socket'` event is not supported. * The `'upgrade'` event is not supported. * Gaining direct access to the underlying `socket` is not supported. ```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/runtime-apis/","name":"Runtime APIs"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/runtime-apis/nodejs/","name":"Node.js compatibility"}},{"@type":"ListItem","position":5,"item":{"@id":"/workers/runtime-apis/nodejs/http/","name":"http"}}]} ``` --- --- title: https description: To use the HTTPS client-side methods (https.get, https.request, etc.), you must enable the enable_nodejs_http_modules compatibility flag in addition to the nodejs_compat flag. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/runtime-apis/nodejs/https.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # https Note To enable built-in Node.js APIs and polyfills, add the nodejs\_compat compatibility flag to your [Wrangler configuration file](https://developers.cloudflare.com/workers/wrangler/configuration/). This also enables nodejs\_compat\_v2 as long as your compatibility date is 2024-09-23 or later. [Learn more about the Node.js compatibility flag and v2](https://developers.cloudflare.com/workers/configuration/compatibility-flags/#nodejs-compatibility-flag). ## Compatibility flags ### Client-side methods To use the HTTPS client-side methods (`https.get`, `https.request`, etc.), you must enable the [enable\_nodejs\_http\_modules](https://developers.cloudflare.com/workers/configuration/compatibility-flags/) compatibility flag in addition to the [nodejs\_compat](https://developers.cloudflare.com/workers/runtime-apis/nodejs/) flag. This flag is automatically enabled for Workers using a [compatibility date](https://developers.cloudflare.com/workers/configuration/compatibility-dates/) of `2025-08-15` or later when `nodejs_compat` is enabled. For Workers using an earlier compatibility date, you can manually enable it by adding the flag to your `wrangler.toml`: TOML ``` compatibility_flags = ["nodejs_compat", "enable_nodejs_http_modules"] ``` ### Server-side methods To use the HTTPS server-side methods (`https.createServer`, `https.Server`, `https.ServerResponse`), you must enable the `enable_nodejs_http_server_modules` compatibility flag in addition to the [nodejs\_compat](https://developers.cloudflare.com/workers/runtime-apis/nodejs/) flag. This flag is automatically enabled for Workers using a [compatibility date](https://developers.cloudflare.com/workers/configuration/compatibility-dates/) of `2025-09-01` or later when `nodejs_compat` is enabled. For Workers using an earlier compatibility date, you can manually enable it by adding the flag to your `wrangler.toml`: TOML ``` compatibility_flags = ["nodejs_compat", "enable_nodejs_http_server_modules"] ``` To use both client-side and server-side methods, enable both flags: TOML ``` compatibility_flags = ["nodejs_compat", "enable_nodejs_http_modules", "enable_nodejs_http_server_modules"] ``` ## get An implementation of the Node.js [\`https.get' ↗](https://nodejs.org/docs/latest/api/https.html#httpsgetoptions-callback) method. The `get` method performs a GET request to the specified URL and invokes the callback with the response. This is a convenience method that simplifies making HTTPS GET requests without manually configuring request options. Because `get` is a wrapper around `fetch(...)`, it may be used only within an exported fetch or similar handler. Outside of such a handler, attempts to use `get` will throw an error. JavaScript ``` import { get } from "node:https"; export default { async fetch() { const { promise, resolve, reject } = Promise.withResolvers(); get("https://example.com", (res) => { let data = ""; res.setEncoding("utf8"); res.on("data", (chunk) => { data += chunk; }); res.on("end", () => { resolve(new Response(data)); }); res.on("error", reject); }).on("error", reject); return promise; }, }; ``` Explain Code The implementation of `get` in Workers is a wrapper around the global[fetch API ↗](https://developers.cloudflare.com/workers/runtime-apis/fetch/)and is therefore subject to the same [limits ↗](https://developers.cloudflare.com/workers/platform/limits/). As shown in the example above, it is necessary to arrange for requests to be correctly awaited in the `fetch` handler using a promise or the fetch may be canceled prematurely when the handler returns. ## request An implementation of the Node.js [\`https.request' ↗](https://nodejs.org/docs/latest/api/https.html#httpsrequestoptions-callback) method. The `request` method creates an HTTPS request with customizable options like method, headers, and body. It provides full control over the request configuration and returns a Node.js [stream.Writable ↗](https://developers.cloudflare.com/workers/runtime-apis/nodejs/streams/) for sending request data. Because `get` is a wrapper around `fetch(...)`, it may be used only within an exported fetch or similar handler. Outside of such a handler, attempts to use `get` will throw an error. The request method accepts all options from [http.request](https://developers.cloudflare.com/workers/runtime-apis/nodejs/http#request) with some differences in default values: * `protocol`: default `https:` * `port`: default `443` * `agent`: default `https.globalAgent` JavaScript ``` import { request } from "node:https"; import { strictEqual, ok } from "node:assert"; export default { async fetch() { const { promise, resolve, reject } = Promise.withResolvers(); const req = request( "https://developers.cloudflare.com/robots.txt", { method: "GET", }, (res) => { strictEqual(res.statusCode, 200); let data = ""; res.setEncoding("utf8"); res.on("data", (chunk) => { data += chunk; }); res.once("error", reject); res.on("end", () => { ok(data.includes("User-agent")); resolve(new Response(data)); }); }, ); req.end(); return promise; }, }; ``` Explain Code The following additional options are not supported: `ca`, `cert`, `ciphers`, `clientCertEngine` (deprecated), `crl`, `dhparam`, `ecdhCurve`, `honorCipherOrder`, `key`, `passphrase`, `pfx`, `rejectUnauthorized`, `secureOptions`, `secureProtocol`, `servername`, `sessionIdContext`, `highWaterMark`. ## createServer An implementation of the Node.js [https.createServer ↗](https://nodejs.org/docs/latest/api/https.html#httpscreateserveroptions-requestlistener) method. The `createServer` method creates an HTTPS server instance that can handle incoming secure requests. It's a convenience function that creates a new `Server` instance and optionally sets up a request listener callback. JavaScript ``` import { createServer } from "node:https"; import { httpServerHandler } from "cloudflare:node"; const server = createServer((req, res) => { res.writeHead(200, { "Content-Type": "text/plain" }); res.end("Hello from Node.js HTTPS server!"); }); server.listen(8080); export default httpServerHandler({ port: 8080 }); ``` Explain Code The `httpServerHandler` function integrates Node.js HTTPS servers with the Cloudflare Workers request model. When a request arrives at your Worker, the handler automatically routes it to your Node.js server running on the specified port. This bridge allows you to use familiar Node.js server patterns while benefiting from the Workers runtime environment, including automatic scaling, edge deployment, and integration with other Cloudflare services. Note Failing to call `close()` on an HTTPS server may result in the server being leaked. To prevent this, call `close()` when you're done with the server, or use explicit resource management: JavaScript ``` import { createServer } from "node:https"; await using server = createServer((req, res) => { res.end("Hello World"); }); // Server will be automatically closed when it goes out of scope ``` ## Agent An implementation of the Node.js [https.Agent ↗](https://nodejs.org/docs/latest/api/https.html#class-httpsagent) class. An [Agent ↗](https://nodejs.org/docs/latest/api/https.html#class-httpsagent) manages HTTPS connection reuse by maintaining request queues per host/port. In the Workers environment, however, such low-level management of the network connection, ports, etc, is not relevant because it is handled by the Cloudflare infrastructure instead. Accordingly, the implementation of `Agent` in Workers is a stub implementation that does not support connection pooling or keep-alive. ## Server An implementation of the Node.js [https.Server ↗](https://nodejs.org/docs/latest/api/https.html#class-httpsserver) class. In Node.js, the `https.Server` class represents an HTTPS server and provides methods for handling incoming secure requests. In Workers, handling of secure requests is provided by the Cloudflare infrastructure so there really is not much difference between using `https.Server` or `http.Server`. The workers runtime provides an implementation for completeness but most workers should probably just use [http.Server](https://developers.cloudflare.com/workers/runtime-apis/nodejs/http#server). JavaScript ``` import { Server } from "node:https"; import { httpServerHandler } from "cloudflare:node"; const server = new Server((req, res) => { res.writeHead(200, { "Content-Type": "application/json" }); res.end(JSON.stringify({ message: "Hello from HTTPS Server!" })); }); server.listen(8080); export default httpServerHandler({ port: 8080 }); ``` The following differences exist between the Workers implementation and Node.js: * Connection management methods such as `closeAllConnections()` and `closeIdleConnections()` are not implemented due to the nature of the Workers environment. * Only `listen()` variants with a port number or no parameters are supported: `listen()`, `listen(0, callback)`, `listen(callback)`, etc. * The following server options are not supported: `maxHeaderSize`, `insecureHTTPParser`, `keepAliveTimeout`, `connectionsCheckingInterval` * TLS/SSL-specific options such as `ca`, `cert`, `key`, `pfx`, `rejectUnauthorized`, `secureProtocol` are not supported in the Workers environment. If you need to use mTLS, use the [mTLS binding](https://developers.cloudflare.com/workers/runtime-apis/bindings/mtls/). ## Other differences between Node.js and Workers implementation of `node:https` Because the Workers implementation of `node:https` is a wrapper around the global `fetch` API, there are some differences in behavior compared to Node.js: * `Connection` headers are not used. Workers will manage connections automatically. * `Content-Length` headers will be handled the same way as in the `fetch` API. If a body is provided, the header will be set automatically and manually set values will be ignored. * `Expect: 100-continue` headers are not supported. * Trailing headers are not supported. * The `'continue'` event is not supported. * The `'information'` event is not supported. * The `'socket'` event is not supported. * The `'upgrade'` event is not supported. * Gaining direct access to the underlying `socket` is not supported. * Configuring TLS-specific options like `ca`, `cert`, `key`, `rejectUnauthorized`, etc, is not supported. ```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/runtime-apis/","name":"Runtime APIs"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/runtime-apis/nodejs/","name":"Node.js compatibility"}},{"@type":"ListItem","position":5,"item":{"@id":"/workers/runtime-apis/nodejs/https/","name":"https"}}]} ``` --- --- title: net description: You can use node:net to create a direct connection to servers via a TCP sockets with net.Socket. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/runtime-apis/nodejs/net.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # net Note To enable built-in Node.js APIs and polyfills, add the nodejs\_compat compatibility flag to your [Wrangler configuration file](https://developers.cloudflare.com/workers/wrangler/configuration/). This also enables nodejs\_compat\_v2 as long as your compatibility date is 2024-09-23 or later. [Learn more about the Node.js compatibility flag and v2](https://developers.cloudflare.com/workers/configuration/compatibility-flags/#nodejs-compatibility-flag). You can use [node:net ↗](https://nodejs.org/api/net.html) to create a direct connection to servers via a TCP sockets with [net.Socket ↗](https://nodejs.org/api/net.html#class-netsocket). These functions use [connect](https://developers.cloudflare.com/workers/runtime-apis/tcp-sockets/#connect) functionality from the built-in `cloudflare:sockets` module. * [ JavaScript ](#tab-panel-7774) * [ TypeScript ](#tab-panel-7775) index.js ``` import net from "node:net"; const exampleIP = "127.0.0.1"; export default { async fetch(req) { const socket = new net.Socket(); socket.connect(4000, exampleIP, function () { console.log("Connected"); }); socket.write("Hello, Server!"); socket.end(); return new Response("Wrote to server", { status: 200 }); }, }; ``` Explain Code index.ts ``` import net from "node:net"; const exampleIP = "127.0.0.1"; export default { async fetch(req): Promise { const socket = new net.Socket(); socket.connect(4000, exampleIP, function () { console.log("Connected"); }); socket.write("Hello, Server!"); socket.end(); return new Response("Wrote to server", { status: 200 }); }, } satisfies ExportedHandler; ``` Explain Code Additionally, other APIs such as [net.BlockList ↗](https://nodejs.org/api/net.html#class-netblocklist)and [net.SocketAddress ↗](https://nodejs.org/api/net.html#class-netsocketaddress) are available. Note that the [net.Server ↗](https://nodejs.org/api/net.html#class-netserver) class is not supported by Workers. The full `node:net` API is documented in the [Node.js documentation for node:net ↗](https://nodejs.org/api/net.html). ``` ``` ```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/runtime-apis/","name":"Runtime APIs"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/runtime-apis/nodejs/","name":"Node.js compatibility"}},{"@type":"ListItem","position":5,"item":{"@id":"/workers/runtime-apis/nodejs/net/","name":"net"}}]} ``` --- --- title: path description: The node:path module provides utilities for working with file and directory paths. The node:path module can be accessed using: image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/runtime-apis/nodejs/path.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # path Note To enable built-in Node.js APIs and polyfills, add the nodejs\_compat compatibility flag to your [Wrangler configuration file](https://developers.cloudflare.com/workers/wrangler/configuration/). This also enables nodejs\_compat\_v2 as long as your compatibility date is 2024-09-23 or later. [Learn more about the Node.js compatibility flag and v2](https://developers.cloudflare.com/workers/configuration/compatibility-flags/#nodejs-compatibility-flag). The [node:path ↗](https://nodejs.org/api/path.html) module provides utilities for working with file and directory paths. The `node:path` module can be accessed using: JavaScript ``` import path from "node:path"; path.join("/foo", "bar", "baz/asdf", "quux", ".."); // Returns: '/foo/bar/baz/asdf' ``` Refer to the [Node.js documentation for path ↗](https://nodejs.org/api/path.html) for more information. ```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/runtime-apis/","name":"Runtime APIs"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/runtime-apis/nodejs/","name":"Node.js compatibility"}},{"@type":"ListItem","position":5,"item":{"@id":"/workers/runtime-apis/nodejs/path/","name":"path"}}]} ``` --- --- title: process description: The process module in Node.js provides a number of useful APIs related to the current process. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/runtime-apis/nodejs/process.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # process Note To enable built-in Node.js APIs and polyfills, add the nodejs\_compat compatibility flag to your [Wrangler configuration file](https://developers.cloudflare.com/workers/wrangler/configuration/). This also enables nodejs\_compat\_v2 as long as your compatibility date is 2024-09-23 or later. [Learn more about the Node.js compatibility flag and v2](https://developers.cloudflare.com/workers/configuration/compatibility-flags/#nodejs-compatibility-flag). The [process ↗](https://nodejs.org/docs/latest/api/process.html) module in Node.js provides a number of useful APIs related to the current process. Initially Workers only supported `nextTick`, `env`, `exit`, `getBuiltinModule`, `platform` and `features` on process, which was then updated with the [enable\_nodejs\_process\_v2](https://developers.cloudflare.com/workers/configuration/compatibility-flags/#enable-process-v2-implementation) flag to include most Node.js process features. Refer to the [Node.js documentation for process ↗](https://nodejs.org/docs/latest/api/process.html) for more information. Workers-specific implementation details apply when adapting Node.js process support for a serverless environment, which are described in more detail below. ## `process.env` In the Node.js implementation of `process.env`, the `env` object is a copy of the environment variables at the time the process was started. In the Workers implementation, there is no process-level environment, so by default `env` is an empty object. You can still set and get values from `env`, and those will be globally persistent for all Workers running in the same isolate and context (for example, the same Workers entry point). When [Node.js compatibility](https://developers.cloudflare.com/workers/runtime-apis/nodejs/) is enabled and the [nodejs\_compat\_populate\_process\_env](https://developers.cloudflare.com/workers/configuration/compatibility-flags/#enable-auto-populating-processenv) compatibility flag is set (enabled by default for compatibility dates on or after 2025-04-01), `process.env` will contain any [environment variables](https://developers.cloudflare.com/workers/configuration/environment-variables/),[secrets](https://developers.cloudflare.com/workers/configuration/secrets/), or [version metadata](https://developers.cloudflare.com/workers/runtime-apis/bindings/version-metadata/) metadata that has been configured on your Worker. Setting any value on `process.env` will coerce that value into a string. ### Alternative: Import `env` from `cloudflare:workers` Instead of using `process.env`, you can [import env from cloudflare:workers](https://developers.cloudflare.com/workers/runtime-apis/bindings/#importing-env-as-a-global) to access environment variables and all other bindings from anywhere in your code. JavaScript ``` import * as process from "node:process"; export default { fetch(req, env) { // Set process.env.FOO to the value of env.FOO if process.env.FOO is not already set // and env.FOO is a string. process.env.FOO ??= (() => { if (typeof env.FOO === "string") { return env.FOO; } })(); }, }; ``` Explain Code It is strongly recommended that you _do not_ replace the entire `process.env` object with the cloudflare `env` object. Doing so will cause you to lose any environment variables that were set previously and will cause unexpected behavior for other Workers running in the same isolate. Specifically, it would cause inconsistency with the `process.env` object when accessed via named imports. JavaScript ``` import * as process from "node:process"; import { env } from "node:process"; process.env === env; // true! they are the same object process.env = {}; // replace the object! Do not do this! process.env === env; // false! they are no longer the same object // From this point forward, any changes to process.env will not be reflected in env, // and vice versa! ``` ## `process.nextTick()` The Workers implementation of `process.nextTick()` is a wrapper for the standard Web Platform API [queueMicrotask() ↗](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/queueMicrotask). JavaScript ``` import { env, nextTick } from "node:process"; env["FOO"] = "bar"; console.log(env["FOO"]); // Prints: bar nextTick(() => { console.log("next tick"); }); ``` ## Stdio [process.stdout ↗](https://nodejs.org/docs/latest/api/process.html#processstdout), [process.stderr ↗](https://nodejs.org/docs/latest/api/process.html#processstderr) and [process.stdin ↗](https://nodejs.org/docs/latest/api/process.html#processstdin) are supported as streams. `stdin` is treated as an empty readable stream.`stdout` and `stderr` are non-TTY writable streams, which output to normal logging output only with `stdout: ` and `stderr: ` prefixing. The line buffer works by storing writes to stdout or stderr until either a newline character `\n` is encountered or until the next microtask, when the log is then flushed to the output. This ensures compatibility with inspector and structured logging outputs. ## Current Working Directory [process.cwd() ↗](https://nodejs.org/docs/latest/api/process.html#processcwd) is the _current working directory_, used as the default path for all filesystem operations, and is initialized to `/bundle`. [process.chdir() ↗](https://nodejs.org/docs/latest/api/process.html#processchdirdirectory) allows modifying the `cwd` and is respected by FS operations when using `enable_nodejs_fs_module`. ## Hrtime While [process.hrtime ↗](https://nodejs.org/docs/latest/api/process.html#processhrtimetime) high-resolution timer is available, it provides an inaccurate timer for compatibility only. ```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/runtime-apis/","name":"Runtime APIs"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/runtime-apis/nodejs/","name":"Node.js compatibility"}},{"@type":"ListItem","position":5,"item":{"@id":"/workers/runtime-apis/nodejs/process/","name":"process"}}]} ``` --- --- title: Streams description: The Node.js streams API is the original API for working with streaming data in JavaScript, predating the WHATWG ReadableStream standard. A stream is an abstract interface for working with streaming data in Node.js. Streams can be readable, writable, or both. All streams are instances of EventEmitter. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/runtime-apis/nodejs/streams.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Streams Note To enable built-in Node.js APIs and polyfills, add the nodejs\_compat compatibility flag to your [Wrangler configuration file](https://developers.cloudflare.com/workers/wrangler/configuration/). This also enables nodejs\_compat\_v2 as long as your compatibility date is 2024-09-23 or later. [Learn more about the Node.js compatibility flag and v2](https://developers.cloudflare.com/workers/configuration/compatibility-flags/#nodejs-compatibility-flag). The [Node.js streams API ↗](https://nodejs.org/api/stream.html) is the original API for working with streaming data in JavaScript, predating the [WHATWG ReadableStream standard ↗](https://streams.spec.whatwg.org/). A stream is an abstract interface for working with streaming data in Node.js. Streams can be readable, writable, or both. All streams are instances of [EventEmitter](https://developers.cloudflare.com/workers/runtime-apis/nodejs/eventemitter/). Where possible, you should use the [WHATWG standard "Web Streams" API ↗](https://streams.spec.whatwg.org/), which is [supported in Workers ↗](https://streams.spec.whatwg.org/). JavaScript ``` import { Readable, Transform } from "node:stream"; import { text } from "node:stream/consumers"; import { pipeline } from "node:stream/promises"; // A Node.js-style Transform that converts data to uppercase // and appends a newline to the end of the output. class MyTransform extends Transform { constructor() { super({ encoding: "utf8" }); } _transform(chunk, _, cb) { this.push(chunk.toString().toUpperCase()); cb(); } _flush(cb) { this.push("\n"); cb(); } } export default { async fetch() { const chunks = [ "hello ", "from ", "the ", "wonderful ", "world ", "of ", "node.js ", "streams!", ]; function nextChunk(readable) { readable.push(chunks.shift()); if (chunks.length === 0) readable.push(null); else queueMicrotask(() => nextChunk(readable)); } // A Node.js-style Readable that emits chunks from the // array... const readable = new Readable({ encoding: "utf8", read() { nextChunk(readable); }, }); const transform = new MyTransform(); await pipeline(readable, transform); return new Response(await text(transform)); }, }; ``` Explain Code Refer to the [Node.js documentation for stream ↗](https://nodejs.org/api/stream.html) for more information. ```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/runtime-apis/","name":"Runtime APIs"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/runtime-apis/nodejs/","name":"Node.js compatibility"}},{"@type":"ListItem","position":5,"item":{"@id":"/workers/runtime-apis/nodejs/streams/","name":"Streams"}}]} ``` --- --- title: StringDecoder description: The node:string_decoder is a legacy utility module that predates the WHATWG standard TextEncoder and TextDecoder API. In most cases, you should use TextEncoder and TextDecoder instead. StringDecoder is available in the Workers runtime primarily for compatibility with existing npm packages that rely on it. StringDecoder can be accessed using: image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/runtime-apis/nodejs/string-decoder.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # StringDecoder Note To enable built-in Node.js APIs and polyfills, add the nodejs\_compat compatibility flag to your [Wrangler configuration file](https://developers.cloudflare.com/workers/wrangler/configuration/). This also enables nodejs\_compat\_v2 as long as your compatibility date is 2024-09-23 or later. [Learn more about the Node.js compatibility flag and v2](https://developers.cloudflare.com/workers/configuration/compatibility-flags/#nodejs-compatibility-flag). The [node:string\_decoder ↗](https://nodejs.org/api/string%5Fdecoder.html) is a legacy utility module that predates the WHATWG standard [TextEncoder](https://developers.cloudflare.com/workers/runtime-apis/encoding/#textencoder) and [TextDecoder](https://developers.cloudflare.com/workers/runtime-apis/encoding/#textdecoder) API. In most cases, you should use `TextEncoder` and `TextDecoder` instead. `StringDecoder` is available in the Workers runtime primarily for compatibility with existing npm packages that rely on it. `StringDecoder` can be accessed using: JavaScript ``` const { StringDecoder } = require("node:string_decoder"); const decoder = new StringDecoder("utf8"); const cent = Buffer.from([0xc2, 0xa2]); console.log(decoder.write(cent)); const euro = Buffer.from([0xe2, 0x82, 0xac]); console.log(decoder.write(euro)); ``` Refer to the [Node.js documentation for string\_decoder ↗](https://nodejs.org/dist/latest-v20.x/docs/api/string%5Fdecoder.html) for more information. ```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/runtime-apis/","name":"Runtime APIs"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/runtime-apis/nodejs/","name":"Node.js compatibility"}},{"@type":"ListItem","position":5,"item":{"@id":"/workers/runtime-apis/nodejs/string-decoder/","name":"StringDecoder"}}]} ``` --- --- title: test description: The MockTracker API in Node.js provides a means of tracking and managing mock objects in a test environment. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/runtime-apis/nodejs/test.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # test Note To enable built-in Node.js APIs and polyfills, add the nodejs\_compat compatibility flag to your [Wrangler configuration file](https://developers.cloudflare.com/workers/wrangler/configuration/). This also enables nodejs\_compat\_v2 as long as your compatibility date is 2024-09-23 or later. [Learn more about the Node.js compatibility flag and v2](https://developers.cloudflare.com/workers/configuration/compatibility-flags/#nodejs-compatibility-flag). ## `MockTracker` The `MockTracker` API in Node.js provides a means of tracking and managing mock objects in a test environment. JavaScript ``` import { mock } from 'node:test'; const fn = mock.fn(); fn(1,2,3); // does nothing... but console.log(fn.mock.callCount()); // Records how many times it was called console.log(fn.mock.calls[0].arguments)); // Recoreds the arguments that were passed each call ``` The full `MockTracker` API is documented in the [Node.js documentation for MockTracker ↗](https://nodejs.org/docs/latest/api/test.html#class-mocktracker). The Workers implementation of `MockTracker` currently does not include an implementation of the [Node.js mock timers API ↗](https://nodejs.org/docs/latest/api/test.html#class-mocktimers). ```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/runtime-apis/","name":"Runtime APIs"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/runtime-apis/nodejs/","name":"Node.js compatibility"}},{"@type":"ListItem","position":5,"item":{"@id":"/workers/runtime-apis/nodejs/test/","name":"test"}}]} ``` --- --- title: timers description: Use node:timers APIs to schedule functions to be executed later. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/runtime-apis/nodejs/timers.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # timers Note To enable built-in Node.js APIs and polyfills, add the nodejs\_compat compatibility flag to your [Wrangler configuration file](https://developers.cloudflare.com/workers/wrangler/configuration/). This also enables nodejs\_compat\_v2 as long as your compatibility date is 2024-09-23 or later. [Learn more about the Node.js compatibility flag and v2](https://developers.cloudflare.com/workers/configuration/compatibility-flags/#nodejs-compatibility-flag). Use [node:timers ↗](https://nodejs.org/api/timers.html) APIs to schedule functions to be executed later. This includes [setTimeout ↗](https://nodejs.org/api/timers.html#settimeoutcallback-delay-args) for calling a function after a delay,[setInterval ↗](https://nodejs.org/api/timers.html#clearintervaltimeout) for calling a function repeatedly, and [setImmediate ↗](https://nodejs.org/api/timers.html#setimmediatecallback-args) for calling a function in the next iteration of the event loop. * [ JavaScript ](#tab-panel-7776) * [ TypeScript ](#tab-panel-7777) index.js ``` import timers from "node:timers"; export default { async fetch() { console.log("first"); const { promise: promise1, resolve: resolve1 } = Promise.withResolvers(); const { promise: promise2, resolve: resolve2 } = Promise.withResolvers(); timers.setTimeout(() => { console.log("last"); resolve1(); }, 10); timers.setTimeout(() => { console.log("next"); resolve2(); }); await Promise.all([promise1, promise2]); return new Response("ok"); }, }; ``` Explain Code index.ts ``` import timers from "node:timers"; export default { async fetch(): Promise { console.log("first"); const { promise: promise1, resolve: resolve1 } = Promise.withResolvers(); const { promise: promise2, resolve: resolve2 } = Promise.withResolvers(); timers.setTimeout(() => { console.log("last"); resolve1(); }, 10); timers.setTimeout(() => { console.log("next"); resolve2(); }); await Promise.all([promise1, promise2]); return new Response("ok"); } } satisfies ExportedHandler; ``` Explain Code Note Due to [security-based restrictions on timers](https://developers.cloudflare.com/workers/reference/security-model/#step-1-disallow-timers-and-multi-threading) in Workers, timers are limited to returning the time of the last I/O. This means that while setTimeout, setInterval, and setImmediate will defer your function execution until after other events have run, they will not delay them for the full time specified. Note When called from a global level (on [globalThis ↗](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global%5FObjects/globalThis)), functions such as `clearTimeout` and `setTimeout` will respect web standards rather than Node.js-specific functionality. For complete Node.js compatibility, you must call functions from the `node:timers` module. The full `node:timers` API is documented in the [Node.js documentation for node:timers ↗](https://nodejs.org/api/timers.html). ```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/runtime-apis/","name":"Runtime APIs"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/runtime-apis/nodejs/","name":"Node.js compatibility"}},{"@type":"ListItem","position":5,"item":{"@id":"/workers/runtime-apis/nodejs/timers/","name":"timers"}}]} ``` --- --- title: tls description: You can use node:tls to create secure connections to external services using TLS (Transport Layer Security). image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/runtime-apis/nodejs/tls.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # tls Note To enable built-in Node.js APIs and polyfills, add the nodejs\_compat compatibility flag to your [Wrangler configuration file](https://developers.cloudflare.com/workers/wrangler/configuration/). This also enables nodejs\_compat\_v2 as long as your compatibility date is 2024-09-23 or later. [Learn more about the Node.js compatibility flag and v2](https://developers.cloudflare.com/workers/configuration/compatibility-flags/#nodejs-compatibility-flag). You can use [node:tls ↗](https://nodejs.org/api/tls.html) to create secure connections to external services using [TLS ↗](https://developer.mozilla.org/en-US/docs/Web/Security/Transport%5FLayer%5FSecurity) (Transport Layer Security). JavaScript ``` import { connect } from "node:tls"; // ... in a request handler ... const connectionOptions = { key: env.KEY, cert: env.CERT }; const socket = connect(url, connectionOptions, () => { if (socket.authorized) { console.log("Connection authorized"); } }); socket.on("data", (data) => { console.log(data); }); socket.on("end", () => { console.log("server ends connection"); }); ``` Explain Code The following APIs are available: * [connect ↗](https://nodejs.org/api/tls.html#tlsconnectoptions-callback) * [TLSSocket ↗](https://nodejs.org/api/tls.html#class-tlstlssocket) * [checkServerIdentity ↗](https://nodejs.org/api/tls.html#tlscheckserveridentityhostname-cert) * [createSecureContext ↗](https://nodejs.org/api/tls.html#tlscreatesecurecontextoptions) All other APIs, including [tls.Server ↗](https://nodejs.org/api/tls.html#class-tlsserver) and [tls.createServer ↗](https://nodejs.org/api/tls.html#tlscreateserveroptions-secureconnectionlistener), are not supported and will throw a `Not implemented` error when called. The full `node:tls` API is documented in the [Node.js documentation for node:tls ↗](https://nodejs.org/api/tls.html). ```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/runtime-apis/","name":"Runtime APIs"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/runtime-apis/nodejs/","name":"Node.js compatibility"}},{"@type":"ListItem","position":5,"item":{"@id":"/workers/runtime-apis/nodejs/tls/","name":"tls"}}]} ``` --- --- title: url description: Returns the Punycode ASCII serialization of the domain. If domain is an invalid domain, the empty string is returned. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/runtime-apis/nodejs/url.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # url Note To enable built-in Node.js APIs and polyfills, add the nodejs\_compat compatibility flag to your [Wrangler configuration file](https://developers.cloudflare.com/workers/wrangler/configuration/). This also enables nodejs\_compat\_v2 as long as your compatibility date is 2024-09-23 or later. [Learn more about the Node.js compatibility flag and v2](https://developers.cloudflare.com/workers/configuration/compatibility-flags/#nodejs-compatibility-flag). ## domainToASCII Returns the Punycode ASCII serialization of the domain. If domain is an invalid domain, the empty string is returned. JavaScript ``` import { domainToASCII } from "node:url"; console.log(domainToASCII("español.com")); // Prints xn--espaol-zwa.com console.log(domainToASCII("中文.com")); // Prints xn--fiq228c.com console.log(domainToASCII("xn--iñvalid.com")); // Prints an empty string ``` ## domainToUnicode Returns the Unicode serialization of the domain. If domain is an invalid domain, the empty string is returned. It performs the inverse operation to `domainToASCII()`. JavaScript ``` import { domainToUnicode } from "node:url"; console.log(domainToUnicode("xn--espaol-zwa.com")); // Prints español.com console.log(domainToUnicode("xn--fiq228c.com")); // Prints 中文.com console.log(domainToUnicode("xn--iñvalid.com")); // Prints an empty string ``` ```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/runtime-apis/","name":"Runtime APIs"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/runtime-apis/nodejs/","name":"Node.js compatibility"}},{"@type":"ListItem","position":5,"item":{"@id":"/workers/runtime-apis/nodejs/url/","name":"url"}}]} ``` --- --- title: util description: The promisify and callbackify APIs in Node.js provide a means of bridging between a Promise-based programming model and a callback-based model. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/runtime-apis/nodejs/util.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # util Note To enable built-in Node.js APIs and polyfills, add the nodejs\_compat compatibility flag to your [Wrangler configuration file](https://developers.cloudflare.com/workers/wrangler/configuration/). This also enables nodejs\_compat\_v2 as long as your compatibility date is 2024-09-23 or later. [Learn more about the Node.js compatibility flag and v2](https://developers.cloudflare.com/workers/configuration/compatibility-flags/#nodejs-compatibility-flag). ## promisify/callbackify The `promisify` and `callbackify` APIs in Node.js provide a means of bridging between a Promise-based programming model and a callback-based model. The `promisify` method allows taking a Node.js-style callback function and converting it into a Promise-returning async function: JavaScript ``` import { promisify } from "node:util"; function foo(args, callback) { try { callback(null, 1); } catch (err) { // Errors are emitted to the callback via the first argument. callback(err); } } const promisifiedFoo = promisify(foo); await promisifiedFoo(args); ``` Explain Code Similarly to `promisify`, `callbackify` converts a Promise-returning async function into a Node.js-style callback function: JavaScript ``` import { callbackify } from 'node:util'; async function foo(args) { throw new Error('boom'); } const callbackifiedFoo = callbackify(foo); callbackifiedFoo(args, (err, value) => { if (err) throw err; }); ``` Explain Code `callbackify` and `promisify` make it easy to handle all of the challenges that come with bridging between callbacks and promises. Refer to the [Node.js documentation for callbackify ↗](https://nodejs.org/dist/latest-v19.x/docs/api/util.html#utilcallbackifyoriginal) and [Node.js documentation for promisify ↗](https://nodejs.org/dist/latest-v19.x/docs/api/util.html#utilpromisifyoriginal) for more information. ## util.types The `util.types` API provides a reliable and efficient way of checking that values are instances of various built-in types. JavaScript ``` import { types } from "node:util"; types.isAnyArrayBuffer(new ArrayBuffer()); // Returns true types.isAnyArrayBuffer(new SharedArrayBuffer()); // Returns true types.isArrayBufferView(new Int8Array()); // true types.isArrayBufferView(Buffer.from("hello world")); // true types.isArrayBufferView(new DataView(new ArrayBuffer(16))); // true types.isArrayBufferView(new ArrayBuffer()); // false function foo() { types.isArgumentsObject(arguments); // Returns true } types.isAsyncFunction(function foo() {}); // Returns false types.isAsyncFunction(async function foo() {}); // Returns true // .. and so on ``` Explain Code Warning The Workers implementation currently does not provide implementations of the `util.types.isExternal()`, `util.types.isProxy()`, `util.types.isKeyObject()`, or `util.type.isWebAssemblyCompiledModule()` APIs. For more about `util.types`, refer to the [Node.js documentation for util.types ↗](https://nodejs.org/dist/latest-v19.x/docs/api/util.html#utiltypes). ## util.MIMEType `util.MIMEType` provides convenience methods that allow you to more easily work with and manipulate [MIME types ↗](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics%5Fof%5FHTTP/MIME%5Ftypes). For example: JavaScript ``` import { MIMEType } from "node:util"; const myMIME = new MIMEType("text/javascript;key=value"); console.log(myMIME.type); // Prints: text console.log(myMIME.essence); // Prints: text/javascript console.log(myMIME.subtype); // Prints: javascript console.log(String(myMIME)); // Prints: application/javascript;key=value ``` Explain Code For more about `util.MIMEType`, refer to the [Node.js documentation for util.MIMEType ↗](https://nodejs.org/api/util.html#class-utilmimetype). ```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/runtime-apis/","name":"Runtime APIs"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/runtime-apis/nodejs/","name":"Node.js compatibility"}},{"@type":"ListItem","position":5,"item":{"@id":"/workers/runtime-apis/nodejs/util/","name":"util"}}]} ``` --- --- title: zlib description: The node:zlib module provides compression functionality implemented using Gzip, Deflate/Inflate, and Brotli. To access it: image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/runtime-apis/nodejs/zlib.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # zlib Note To enable built-in Node.js APIs and polyfills, add the nodejs\_compat compatibility flag to your [Wrangler configuration file](https://developers.cloudflare.com/workers/wrangler/configuration/). This also enables nodejs\_compat\_v2 as long as your compatibility date is 2024-09-23 or later. [Learn more about the Node.js compatibility flag and v2](https://developers.cloudflare.com/workers/configuration/compatibility-flags/#nodejs-compatibility-flag). The node:zlib module provides compression functionality implemented using Gzip, Deflate/Inflate, and Brotli. To access it: JavaScript ``` import zlib from "node:zlib"; ``` The full `node:zlib` API is documented in the [Node.js documentation for node:zlib ↗](https://nodejs.org/api/zlib.html). ```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/runtime-apis/","name":"Runtime APIs"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/runtime-apis/nodejs/","name":"Node.js compatibility"}},{"@type":"ListItem","position":5,"item":{"@id":"/workers/runtime-apis/nodejs/zlib/","name":"zlib"}}]} ``` --- --- title: Performance and timers description: Measure timing, performance, and timing of subrequests and other operations. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/runtime-apis/performance.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Performance and timers ## Background The Workers runtime supports a subset of the [Performance API ↗](https://developer.mozilla.org/en-US/docs/Web/API/Performance), used to measure timing and performance, as well as timing of subrequests and other operations. ### `performance.now()` The [performance.now() method ↗](https://developer.mozilla.org/en-US/docs/Web/API/Performance/now) returns timestamp in milliseconds, representing the time elapsed since `performance.timeOrigin`. When Workers are deployed to Cloudflare, as a security measure to [mitigate against Spectre attacks](https://developers.cloudflare.com/workers/reference/security-model/#step-1-disallow-timers-and-multi-threading), APIs that return timers, including [performance.now() ↗](https://developer.mozilla.org/en-US/docs/Web/API/Performance/now) and [Date.now() ↗](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global%5FObjects/Date/now), only advance or increment after I/O occurs. Consider the following examples: Time is frozen — start will have the exact same value as end. ``` const start = performance.now(); for (let i = 0; i < 1e6; i++) { // do expensive work } const end = performance.now(); const timing = end - start; // 0 ``` Time advances, because a subrequest has occurred between start and end. ``` const start = performance.now(); const response = await fetch("https://developers.cloudflare.com/"); const end = performance.now(); const timing = end - start; // duration of the subrequest to developers.cloudflare.com ``` By wrapping a subrequest in calls to `performance.now()` or `Date.now()` APIs, you can measure the timing of a subrequest, fetching a key from KV, an object from R2, or any other form of I/O in your Worker. In local development, however, timers will increment regardless of whether I/O happens or not. This means that if you need to measure timing of a piece of code that is CPU intensive, that does not involve I/O, you can run your Worker locally, via [Wrangler](https://developers.cloudflare.com/workers/wrangler/), which uses the open-source Workers runtime, [workerd ↗](https://github.com/cloudflare/workerd) — the same runtime that your Worker runs in when deployed to Cloudflare. ### `performance.timeOrigin` The [performance.timeOrigin ↗](https://developer.mozilla.org/en-US/docs/Web/API/Performance/timeOrigin) API is a read-only property that returns a baseline timestamp to base other measurements off of. In the Workers runtime, the `timeOrigin` property returns 0. ```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/runtime-apis/","name":"Runtime APIs"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/runtime-apis/performance/","name":"Performance and timers"}}]} ``` --- --- title: Request description: Interface that represents an HTTP request. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/runtime-apis/request.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Request The [Request ↗](https://developer.mozilla.org/en-US/docs/Web/API/Request/Request) interface represents an HTTP request and is part of the [Fetch API](https://developers.cloudflare.com/workers/runtime-apis/fetch/). ## Background The most common way you will encounter a `Request` object is as a property of an incoming request: JavaScript ``` export default { async fetch(request, env, ctx) { return new Response('Hello World!'); }, }; ``` You may also want to construct a `Request` yourself when you need to modify a request object, because the incoming `request` parameter that you receive from the [fetch() handler](https://developers.cloudflare.com/workers/runtime-apis/handlers/fetch/) is immutable. JavaScript ``` export default { async fetch(request, env, ctx) { const url = "https://example.com"; const modifiedRequest = new Request(url, request); // ... }, }; ``` The [fetch() handler](https://developers.cloudflare.com/workers/runtime-apis/handlers/fetch/) invokes the `Request` constructor. The [RequestInit](#options) and [RequestInitCfProperties](#the-cf-property-requestinitcfproperties) types defined below also describe the valid parameters that can be passed to the [fetch() handler](https://developers.cloudflare.com/workers/runtime-apis/handlers/fetch/). --- ## Constructor JavaScript ``` let request = new Request(input, options) ``` ### Parameters * `input` string | Request * Either a string that contains a URL, or an existing `Request` object. * `options` options optional * Optional options object that contains settings to apply to the `Request`. #### `options` An object containing properties that you want to apply to the request. * `cache` `undefined | 'no-store' | 'no-cache'` optional * Standard HTTP `cache` header. Only `cache: 'no-store'` and `cache: 'no-cache'` are supported. Any other cache header will result in a `TypeError` with the message `Unsupported cache mode: `. * `cf` RequestInitCfProperties optional * Cloudflare-specific properties that can be set on the `Request` that control how Cloudflare’s global network handles the request. * `method` ` string ` optional * The HTTP request method. The default is `GET`. In Workers, all [HTTP request methods ↗](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Methods) are supported, except for [CONNECT ↗](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Methods/CONNECT). * `headers` Headers optional * A [Headers object ↗](https://developer.mozilla.org/en-US/docs/Web/API/Headers). * `body` string | ReadableStream | FormData | URLSearchParams optional * The request body, if any. * Note that a request using the GET or HEAD method cannot have a body. * `redirect` ` string ` optional * The redirect mode to use: `follow`, `error`, or `manual`. The default for a new `Request` object is `follow`. Note, however, that the incoming `Request` property of a `FetchEvent` will have redirect mode `manual`. * `signal` AbortSignal optional * If provided, the request can be canceled by triggering an abort on the corresponding `AbortController`. #### The `cf` property (`RequestInitCfProperties`) An object containing Cloudflare-specific properties that can be set on the `Request` object. For example: JavaScript ``` // Disable ScrapeShield for this request. fetch(event.request, { cf: { scrapeShield: false } }) ``` Invalid or incorrectly-named keys in the `cf` object will be silently ignored. Consider using TypeScript and generating types by running [wrangler types](https://developers.cloudflare.com/workers/languages/typescript/#generate-types) to ensure proper use of the `cf` object. * `apps` ` boolean ` optional * Whether [Cloudflare Apps ↗](https://www.cloudflare.com/apps/) should be enabled for this request. Defaults to `true`. * `cacheEverything` ` boolean ` optional * Treats all content as static and caches all [file types](https://developers.cloudflare.com/cache/concepts/default-cache-behavior#default-cached-file-extensions) beyond the Cloudflare default cached content. Respects cache headers from the origin web server. This is equivalent to setting the Page Rule [**Cache Level** (to **Cache Everything**)](https://developers.cloudflare.com/rules/page-rules/reference/settings/). Defaults to `false`. This option applies to `GET` and `HEAD` request methods only. * `cacheKey` ` string ` optional * A request’s cache key is what determines if two requests are the same for caching purposes. If a request has the same cache key as some previous request, then Cloudflare can serve the same cached response for both. * `cacheTags` Array optional * This option appends additional [**Cache-Tag**](https://developers.cloudflare.com/cache/how-to/purge-cache/purge-by-tags/) headers to the response from the origin server. This allows for purges of cached content based on tags provided by the Worker, without modifications to the origin server. This is performed using the [**Purge by Tag**](https://developers.cloudflare.com/cache/how-to/purge-cache/purge-by-tags/#purge-using-cache-tags) feature. * `cacheTtl` ` number ` optional * This option forces Cloudflare to cache the response for this request, regardless of what headers are seen on the response. This is equivalent to setting two Page Rules: [**Edge Cache TTL**](https://developers.cloudflare.com/cache/how-to/edge-browser-cache-ttl/) and [**Cache Level** (to **Cache Everything**)](https://developers.cloudflare.com/rules/page-rules/reference/settings/). The value must be zero or a positive number. A value of `0` indicates that the cache asset expires immediately. This option applies to `GET` and `HEAD` request methods only. * `cacheTtlByStatus` `{ [key: string]: number }` optional * This option is a version of the `cacheTtl` feature which chooses a TTL based on the response’s status code. If the response to this request has a status code that matches, Cloudflare will cache for the instructed time and override cache instructives sent by the origin. For example: `{ "200-299": 86400, "404": 1, "500-599": 0 }`. The value can be any integer, including zero and negative integers. A value of `0` indicates that the cache asset expires immediately. Any negative value instructs Cloudflare not to cache at all. This option applies to `GET` and `HEAD` request methods only. * `image` Object | null optional * Enables [Image Resizing](https://developers.cloudflare.com/images/transform-images/) for this request. The possible values are described in [Transform images via Workers](https://developers.cloudflare.com/images/transform-images/transform-via-workers/) documentation. * `polish` ` string ` optional * Sets [Polish ↗](https://blog.cloudflare.com/introducing-polish-automatic-image-optimizati/) mode. The possible values are `lossy`, `lossless` or `off`. * `resolveOverride` ` string ` optional * Directs the request to an alternate origin server by overriding the DNS lookup. The value of `resolveOverride` specifies an alternate hostname which will be used when determining the origin IP address, instead of using the hostname specified in the URL. The `Host` header of the request will still match what is in the URL. Thus, `resolveOverride` allows a request to be sent to a different server than the URL / `Host` header specifies. However, `resolveOverride` will only take effect if both the URL host and the host specified by `resolveOverride` are within your zone. If either specifies a host from a different zone / domain, then the option will be ignored for security reasons. If you need to direct a request to a host outside your zone (while keeping the `Host` header pointing within your zone), first create a CNAME record within your zone pointing to the outside host, and then set `resolveOverride` to point at the CNAME record. Note that, for security reasons, it is not possible to set the `Host` header to specify a host outside of your zone unless the request is actually being sent to that host. * `scrapeShield` ` boolean ` optional * Whether [ScrapeShield ↗](https://blog.cloudflare.com/introducing-scrapeshield-discover-defend-dete/) should be enabled for this request, if otherwise configured for this zone. Defaults to `true`. * `webp` ` boolean ` optional * Enables or disables [WebP ↗](https://blog.cloudflare.com/a-very-webp-new-year-from-cloudflare/) image format in [Polish](https://developers.cloudflare.com/images/polish/). --- ## Properties All properties of an incoming `Request` object (the request you receive from the [fetch() handler](https://developers.cloudflare.com/workers/runtime-apis/handlers/fetch/)) are read-only. To modify the properties of an incoming request, create a new `Request` object and pass the options to modify to its [constructor](#constructor). * `body` ReadableStream read-only * Stream of the body contents. * `bodyUsed` Boolean read-only * Declares whether the body has been used in a response yet. * `cf` IncomingRequestCfProperties read-only * An object containing properties about the incoming request provided by Cloudflare’s global network. * This property is read-only (unless created from an existing `Request`). To modify its values, pass in the new values on the [cf key of the init options argument](https://developers.cloudflare.com/workers/runtime-apis/request/#the-cf-property-requestinitcfproperties) when creating a new `Request` object. * `headers` Headers read-only * A [Headers object ↗](https://developer.mozilla.org/en-US/docs/Web/API/Headers). * Compared to browsers, Cloudflare Workers imposes very few restrictions on what headers you are allowed to send. For example, a browser will not allow you to set the `Cookie` header, since the browser is responsible for handling cookies itself. Workers, however, has no special understanding of cookies, and treats the `Cookie` header like any other header. Warning If the response is a redirect and the redirect mode is set to `follow` (see below), then all headers will be forwarded to the redirect destination, even if the destination is a different hostname or domain. This includes sensitive headers like `Cookie`, `Authorization`, or any application-specific headers. If this is not the behavior you want, you should set redirect mode to `manual` and implement your own redirect policy. Note that redirect mode defaults to `manual` for requests that originated from the Worker's client, so this warning only applies to `fetch()`es made by a Worker that are not proxying the original request. * `method` string read-only * Contains the request’s method, for example, `GET`, `POST`, etc. * `redirect` string read-only * The redirect mode to use: `follow`, `error`, or `manual`. The `fetch` method will automatically follow redirects if the redirect mode is set to `follow`. If set to `manual`, the `3xx` redirect response will be returned to the caller as-is. The default for a new `Request` object is `follow`. Note, however, that the incoming `Request` property of a `FetchEvent` will have redirect mode `manual`. * `signal` AbortSignal read-only * The `AbortSignal` corresponding to this request. If you use the [enable\_request\_signal](https://developers.cloudflare.com/workers/configuration/compatibility-flags/#enable-requestsignal-for-incoming-requests) compatibility flag, you can attach an event listener to the signal. This allows you to perform cleanup tasks or write to logs before your Worker's invocation ends. For example, if you run the Worker below, and then abort the request from the client, a log will be written: * [ JavaScript ](#tab-panel-7778) * [ TypeScript ](#tab-panel-7779) index.js ``` export default { async fetch(request, env, ctx) { // This sets up an event listener that will be called if the client disconnects from your // worker. request.signal.addEventListener("abort", () => { console.log("The request was aborted!"); }); const { readable, writable } = new IdentityTransformStream(); sendPing(writable); return new Response(readable, { headers: { "Content-Type": "text/plain" }, }); }, }; async function sendPing(writable) { const writer = writable.getWriter(); const enc = new TextEncoder(); for (;;) { // Send 'ping' every second to keep the connection alive await writer.write(enc.encode("ping\r\n")); await scheduler.wait(1000); } } ``` Explain Code index.ts ``` export default { async fetch(request, env, ctx): Promise { // This sets up an event listener that will be called if the client disconnects from your // worker. request.signal.addEventListener('abort', () => { console.log('The request was aborted!'); }); const { readable, writable } = new IdentityTransformStream(); sendPing(writable); return new Response(readable, { headers: { 'Content-Type': 'text/plain' } }); }, } satisfies ExportedHandler; async function sendPing(writable: WritableStream): Promise { const writer = writable.getWriter(); const enc = new TextEncoder(); for (;;) { // Send 'ping' every second to keep the connection alive await writer.write(enc.encode('ping\r\n')); await scheduler.wait(1000); } } ``` Explain Code * `url` string read-only * Contains the URL of the request. ### `IncomingRequestCfProperties` In addition to the properties on the standard [Request ↗](https://developer.mozilla.org/en-US/docs/Web/API/Request) object, the `request.cf` object on an inbound `Request` contains information about the request provided by Cloudflare’s global network. All plans have access to: * `asn` Number * ASN of the incoming request, for example, `395747`. * `asOrganization` string * The organization which owns the ASN of the incoming request, for example, `Google Cloud`. * `botManagement` Object | null * Only set when using Cloudflare Bot Management. Object with the following properties: `score`, `verifiedBot`, `staticResource`, `ja3Hash`, `ja4`, and `detectionIds`. Refer to [Bot Management Variables](https://developers.cloudflare.com/bots/reference/bot-management-variables/) for more details. * `clientAcceptEncoding` string | null * If Cloudflare replaces the value of the `Accept-Encoding` header, the original value is stored in the `clientAcceptEncoding` property, for example, `"gzip, deflate, br"`. * `clientQuicRtt` number | undefined * The smoothed round-trip time (RTT) between Cloudflare and the client for QUIC connections, in milliseconds. Only present when the client connected over QUIC (HTTP/3). For example, `42`. * `clientTcpRtt` number | undefined * The smoothed round-trip time (RTT) between the client and Cloudflare for TCP connections, in milliseconds. Only present when the client connected over TCP (HTTP/1 and HTTP/2). For example, `22`. * `colo` string * The three-letter [IATA ↗](https://en.wikipedia.org/wiki/IATA%5Fairport%5Fcode) airport code of the data center that the request hit, for example, `"DFW"`. * `country` string | null * Country of the incoming request. The two-letter country code in the request. This is the same value as that provided in the `CF-IPCountry` header, for example, `"US"`. * `edgeL4` Object | undefined * Layer 4 transport statistics for the connection between the client and Cloudflare. Contains the following property: * `deliveryRate` number - The most recent data delivery rate estimate for the connection, in bytes per second. For example, `123456`. * `isEUCountry` string | null * If the country of the incoming request is in the EU, this will return `"1"`. Otherwise, this property is either omitted or `false`. * `httpProtocol` string * HTTP Protocol, for example, `"HTTP/2"`. * `hostMetadata` Object | undefined * Only populated when the incoming request is from a zone with custom hostname metadata. Refer to the Cloudflare for Platforms documentation for more about what you can add as [custom hostname metadata](https://developers.cloudflare.com/cloudflare-for-platforms/cloudflare-for-saas/domain-support/custom-metadata/), and how it is exposed on the `hostMetadata` field. * `requestPriority` string | null * The browser-requested prioritization information in the request object, for example, `"weight=192;exclusive=0;group=3;group-weight=127"`. * `tlsCipher` string * The cipher for the connection to Cloudflare, for example, `"AEAD-AES128-GCM-SHA256"`. * `tlsClientAuth` Object | null * Various details about the client certificate (for mTLS connections). Refer to [Client certificate variables](https://developers.cloudflare.com/ssl/client-certificates/client-certificate-variables/) for more details. * `tlsClientCiphersSha1` string * The SHA-1 hash (Base64-encoded) of the cipher suite sent by the client during the TLS handshake, encoded in big-endian format. For example, `"GXSPDLP4G3X+prK73a4wBuOaHRc="`. * `tlsClientExtensionsSha1` string * The SHA-1 hash (Base64-encoded) of the TLS client extensions sent during the handshake, encoded in big-endian format. For example, `"OWFiM2I5ZDc0YWI0YWYzZmFkMGU0ZjhlYjhiYmVkMjgxNTU5YTU2Mg=="`. * `tlsClientExtensionsSha1Le` string * The SHA-1 hash (Base64-encoded) of the TLS client extensions sent during the handshake, encoded in little-endian format. For example, `"7zIpdDU5pvFPPBI2/PCzqbaXnRA="`. * `tlsClientHelloLength` string * The length of the client hello message sent in a [TLS handshake ↗](https://www.cloudflare.com/learning/ssl/what-happens-in-a-tls-handshake/). For example, `"508"`. Specifically, the length of the bytestring of the client hello. * `tlsClientRandom` string * The value of the 32-byte random value provided by the client in a [TLS handshake ↗](https://www.cloudflare.com/learning/ssl/what-happens-in-a-tls-handshake/). Refer to [RFC 8446 ↗](https://datatracker.ietf.org/doc/html/rfc8446#section-4.1.2) for more details. * `tlsVersion` string * The TLS version of the connection to Cloudflare, for example, `TLSv1.3`. * `city` string | null * City of the incoming request, for example, `"Austin"`. * `continent` string | null * Continent of the incoming request, for example, `"NA"`. * `latitude` string | null * Latitude of the incoming request, for example, `"30.27130"`. * `longitude` string | null * Longitude of the incoming request, for example, `"-97.74260"`. * `postalCode` string | null * Postal code of the incoming request, for example, `"78701"`. * `metroCode` string | null * Metro code (DMA) of the incoming request, for example, `"635"`. * `region` string | null * If known, the [ISO 3166-2 ↗](https://en.wikipedia.org/wiki/ISO%5F3166-2) name for the first level region associated with the IP address of the incoming request, for example, `"Texas"`. * `regionCode` string | null * If known, the [ISO 3166-2 ↗](https://en.wikipedia.org/wiki/ISO%5F3166-2) code for the first-level region associated with the IP address of the incoming request, for example, `"TX"`. * `timezone` string * Timezone of the incoming request, for example, `"America/Chicago"`. Warning The `request.cf` object is not available in the Cloudflare Workers dashboard or Playground preview editor. --- ## Methods ### Instance methods These methods are only available on an instance of a `Request` object or through its prototype. * `clone()` : Request * Creates a copy of the `Request` object. * `arrayBuffer()` : Promise * Returns a promise that resolves with an [ArrayBuffer ↗](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global%5FObjects/ArrayBuffer) representation of the request body. * `formData()` : Promise * Returns a promise that resolves with a [FormData ↗](https://developer.mozilla.org/en-US/docs/Web/API/FormData) representation of the request body. * `json()` : Promise * Returns a promise that resolves with a JSON representation of the request body. * `text()` : Promise * Returns a promise that resolves with a string (text) representation of the request body. --- ## The `Request` context Each time a Worker is invoked by an incoming HTTP request, the [fetch() handler](https://developers.cloudflare.com/workers/runtime-apis/handlers/fetch) is called on your Worker. The `Request` context starts when the `fetch()` handler is called, and asynchronous tasks (such as making a subrequest using the [fetch() API](https://developers.cloudflare.com/workers/runtime-apis/fetch/)) can only be run inside the `Request` context: JavaScript ``` export default { async fetch(request, env, ctx) { // Request context starts here return new Response('Hello World!'); }, }; ``` ### When passing a promise to fetch event `.respondWith()` If you pass a Response promise to the fetch event `.respondWith()` method, the request context is active during any asynchronous tasks which run before the Response promise has settled. You can pass the event to an async handler, for example: JavaScript ``` addEventListener("fetch", event => { event.respondWith(eventHandler(event)) }) // No request context available here async function eventHandler(event){ // Request context available here return new Response("Hello, Workers!") } ``` Explain Code ### Errors when attempting to access an inactive `Request` context Any attempt to use APIs such as `fetch()` or access the `Request` context during script startup will throw an exception: JavaScript ``` const promise = fetch("https://example.com/") // Error async function eventHandler(event){..} ``` This code snippet will throw during script startup, and the `"fetch"` event listener will never be registered. --- ### Set the `Content-Length` header The `Content-Length` header will be automatically set by the runtime based on whatever the data source for the `Request` is. Any value manually set by user code in the `Headers` will be ignored. To have a `Content-Length` header with a specific value specified, the `body` of the `Request` must be either a `FixedLengthStream` or a fixed-length value just as a string or `TypedArray`. A `FixedLengthStream` is an identity `TransformStream` that permits only a fixed number of bytes to be written to it. JavaScript ``` const { writable, readable } = new FixedLengthStream(11); const enc = new TextEncoder(); const writer = writable.getWriter(); writer.write(enc.encode("hello world")); writer.end(); const req = new Request('https://example.org', { method: 'POST', body: readable }); ``` Using any other type of `ReadableStream` as the body of a request will result in Chunked-Encoding being used. --- ## Differences The Workers implementation of the `Request` interface includes several extensions to the web standard `Request` API. These differences are intentional and provide additional functionality specific to the Workers runtime. TypeScript users Workers type definitions (from `@cloudflare/workers-types` or generated via [wrangler types](https://developers.cloudflare.com/workers/wrangler/commands/general/#types)) define a `Request` type that includes Workers-specific properties like `cf`. This type is not directly compatible with the standard `Request` type from `lib.dom.d.ts`. If you are working with code that uses both Workers types and standard web types, you may need to use type assertions or create a new `Request` object. ### The `cf` property Workers adds a `cf` property to the `Request` object that contains Cloudflare-specific metadata about the incoming request. This property is not part of the web standard, and is only available in the Workers runtime. Refer to [IncomingRequestCfProperties](#incomingrequestcfproperties) for details. ### The `headers` property The `headers` property returns a Workers-specific [Headers](https://developers.cloudflare.com/workers/runtime-apis/headers/) object that includes additional methods like `getAll()` for `Set-Cookie` headers. Refer to the [Headers documentation](https://developers.cloudflare.com/workers/runtime-apis/headers/#differences) for details on how the Workers `Headers` implementation differs from the web standard. ### Immutability Incoming `Request` objects passed to the [fetch() handler](https://developers.cloudflare.com/workers/runtime-apis/handlers/fetch/) are immutable. To modify properties of an incoming request, you must create a new `Request` object. --- ## Related resources * [Examples: Modify request property](https://developers.cloudflare.com/workers/examples/modify-request-property/) * [Examples: Accessing the cf object](https://developers.cloudflare.com/workers/examples/accessing-the-cloudflare-object/) * [Reference: Response](https://developers.cloudflare.com/workers/runtime-apis/response/) * Write your Worker code in [ES modules syntax](https://developers.cloudflare.com/workers/reference/migrate-to-module-workers/) for an optimized experience. ```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/runtime-apis/","name":"Runtime APIs"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/runtime-apis/request/","name":"Request"}}]} ``` --- --- title: Response description: Interface that represents an HTTP response. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/runtime-apis/response.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Response The `Response` interface represents an HTTP response and is part of the Fetch API. --- ## Constructor JavaScript ``` let response = new Response(body, init); ``` ### Parameters * `body` optional * An object that defines the body text for the response. Can be `null` or any one of the following types: * BufferSource * FormData * ReadableStream * URLSearchParams * USVString * `init` optional * An `options` object that contains custom settings to apply to the response. Valid options for the `options` object include: * `cf` any | null * An object that contains Cloudflare-specific information. This object is not part of the Fetch API standard and is only available in Cloudflare Workers. This field is only used by consumers of the Response for informational purposes and does not have any impact on Workers behavior. * `encodeBody` string * Workers have to compress data according to the `content-encoding` header when transmitting, to serve data that is already compressed, this property has to be set to `"manual"`, otherwise the default is `"automatic"`. * `headers` Headers | ByteString * Any headers to add to your response that are contained within a [Headers](https://developers.cloudflare.com/workers/runtime-apis/request/#parameters) object or object literal of [ByteString ↗](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global%5FObjects/String) key-value pairs. * `status` int * The status code for the response, such as `200`. * `statusText` string * The status message associated with the status code, such as, `OK`. * `webSocket` WebSocket | null * This is present in successful WebSocket handshake responses. For example, if a client sends a WebSocket upgrade request to an origin and a Worker intercepts the request and then forwards it to the origin and the origin replies with a successful WebSocket upgrade response, the Worker sees `response.webSocket`. This establishes a WebSocket connection proxied through a Worker. Note that you cannot intercept data flowing over a WebSocket connection. ## Properties * `response.body` Readable Stream * A getter to get the body contents. * `response.bodyUsed` boolean * A boolean indicating if the body was used in the response. * `response.headers` Headers * The headers for the response. * `response.ok` boolean * A boolean indicating if the response was successful (status in the range `200`\-`299`). * `response.redirected` boolean * A boolean indicating if the response is the result of a redirect. If so, its URL list has more than one entry. * `response.status` int * The status code of the response (for example, `200` to indicate success). * `response.statusText` string * The status message corresponding to the status code (for example, `OK` for `200`). * `response.url` string * The URL of the response. The value is the final URL obtained after any redirects. * `response.webSocket` WebSocket? * This is present in successful WebSocket handshake responses. For example, if a client sends a WebSocket upgrade request to an origin and a Worker intercepts the request and then forwards it to the origin and the origin replies with a successful WebSocket upgrade response, the Worker sees `response.webSocket`. This establishes a WebSocket connection proxied through a Worker. Note that you cannot intercept data flowing over a WebSocket connection. ## Methods ### Instance methods * `clone()` : Response * Creates a clone of a [Response](#response) object. * `json()` : Response * Creates a new response with a JSON-serialized payload. * `redirect()` : Response * Creates a new response with a different URL. ### Additional instance methods `Response` implements the [Body ↗](https://developer.mozilla.org/en-US/docs/Web/API/Fetch%5FAPI/Using%5FFetch#body) mixin of the [Fetch API ↗](https://developer.mozilla.org/en-US/docs/Web/API/Fetch%5FAPI), and therefore `Response` instances additionally have the following methods available: * `arrayBuffer()` : Promise * Takes a [Response](#response) stream, reads it to completion, and returns a promise that resolves with an [ArrayBuffer ↗](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global%5FObjects/ArrayBuffer). * `formData()` : Promise * Takes a [Response](#response) stream, reads it to completion, and returns a promise that resolves with a [FormData ↗](https://developer.mozilla.org/en-US/docs/Web/API/FormData) object. * `json()` : Promise * Takes a [Response](#response) stream, reads it to completion, and returns a promise that resolves with the result of parsing the body text as [JSON ↗](https://developer.mozilla.org/en-US/docs/Web/). * `text()` : Promise * Takes a [Response](#response) stream, reads it to completion, and returns a promise that resolves with a [USVString ↗](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global%5FObjects/String) (text). ### Set the `Content-Length` header The `Content-Length` header will be automatically set by the runtime based on whatever the data source for the `Response` is. Any value manually set by user code in the `Headers` will be ignored. To have a `Content-Length` header with a specific value specified, the `body` of the `Response` must be either a `FixedLengthStream` or a fixed-length value just as a string or `TypedArray`. A `FixedLengthStream` is an identity `TransformStream` that permits only a fixed number of bytes to be written to it. JavaScript ``` const { writable, readable } = new FixedLengthStream(11); const enc = new TextEncoder(); const writer = writable.getWriter(); writer.write(enc.encode("hello world")); writer.end(); return new Response(readable); ``` Using any other type of `ReadableStream` as the body of a response will result in chunked encoding being used. --- ## Differences The Workers implementation of the `Response` interface includes several extensions to the web standard `Response` API. These differences are intentional and provide additional functionality specific to the Workers runtime. TypeScript users Workers type definitions (from `@cloudflare/workers-types` or generated via [wrangler types](https://developers.cloudflare.com/workers/wrangler/commands/general/#types)) define a `Response` type that includes Workers-specific properties like `cf` and `webSocket`. This type is not directly compatible with the standard `Response` type from `lib.dom.d.ts`. If you are working with code that uses both Workers types and standard web types, you may need to use type assertions. ### The `cf` property Workers adds an optional `cf` property to the `Response` object. This property can be set in the `ResponseInit` options and is used for informational purposes by consumers of the Response. It does not affect Workers behavior. ### The `webSocket` property Workers adds a `webSocket` property to the `Response` object to support WebSocket connections. This property is present in successful WebSocket handshake responses. Refer to [WebSockets](https://developers.cloudflare.com/workers/runtime-apis/websockets/) for more information. ### The `encodeBody` option Workers adds an `encodeBody` option in `ResponseInit` that controls how the response body is compressed. Set this to `"manual"` when serving pre-compressed data to prevent automatic compression. ### The `headers` property The `headers` property returns a Workers-specific [Headers](https://developers.cloudflare.com/workers/runtime-apis/headers/) object that includes additional methods like `getAll()` for `Set-Cookie` headers. Refer to the [Headers documentation](https://developers.cloudflare.com/workers/runtime-apis/headers/#differences) for details on how the Workers `Headers` implementation differs from the web standard. --- ## Related resources * [Examples: Modify response](https://developers.cloudflare.com/workers/examples/modify-response/) * [Examples: Conditional response](https://developers.cloudflare.com/workers/examples/conditional-response/) * [Reference: Request](https://developers.cloudflare.com/workers/runtime-apis/request/) * Write your Worker code in [ES modules syntax](https://developers.cloudflare.com/workers/reference/migrate-to-module-workers/) for an optimized experience. ```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/runtime-apis/","name":"Runtime APIs"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/runtime-apis/response/","name":"Response"}}]} ``` --- --- title: Remote-procedure call (RPC) description: The built-in, JavaScript-native RPC system built into Workers and Durable Objects. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) ### Tags [ RPC ](https://developers.cloudflare.com/search/?tags=RPC) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/runtime-apis/rpc/index.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Remote-procedure call (RPC) Note To use RPC, [define a compatibility date](https://developers.cloudflare.com/workers/configuration/compatibility-dates) of `2024-04-03` or higher, or include `rpc` in your [compatibility flags](https://developers.cloudflare.com/workers/configuration/compatibility-flags/#nodejs-compatibility-flag). Workers provide a built-in, JavaScript-native [RPC (Remote Procedure Call) ↗](https://en.wikipedia.org/wiki/Remote%5Fprocedure%5Fcall) system, allowing you to: * Define public methods on your Worker that can be called by other Workers on the same Cloudflare account, via [Service Bindings](https://developers.cloudflare.com/workers/runtime-apis/bindings/service-bindings/rpc) * Define public methods on [Durable Objects](https://developers.cloudflare.com/durable-objects) that can be called by other workers on the same Cloudflare account that declare a binding to it. The RPC system is designed to feel as similar as possible to calling a JavaScript function in the same Worker. In most cases, you should be able to write code in the same way you would if everything was in a single Worker. ## Example For example, if Worker B implements the public method `add(a, b)`: * [ wrangler.jsonc ](#tab-panel-7798) * [ wrangler.toml ](#tab-panel-7799) JSONC ``` { "$schema": "./node_modules/wrangler/config-schema.json", "name": "worker_b", "main": "./src/workerB.js" } ``` TOML ``` "$schema" = "./node_modules/wrangler/config-schema.json" name = "worker_b" main = "./src/workerB.js" ``` * [ JavaScript ](#tab-panel-7815) * [ TypeScript ](#tab-panel-7816) * [ Python ](#tab-panel-7817) JavaScript ``` import { WorkerEntrypoint } from "cloudflare:workers"; export default class extends WorkerEntrypoint { async fetch() { return new Response("Hello from Worker B"); } add(a, b) { return a + b; } } ``` Explain Code TypeScript ``` import { WorkerEntrypoint } from "cloudflare:workers"; export default class extends WorkerEntrypoint { async fetch() { return new Response("Hello from Worker B"); } add(a: number, b: number) { return a + b; } } ``` Explain Code Python ``` from workers import WorkerEntrypoint, Response class Default(WorkerEntrypoint): async def fetch(self, request): return Response("Hello from Worker B") def add(self, a: int, b: int) -> int: return a + b ``` Worker A can declare a [binding](https://developers.cloudflare.com/workers/runtime-apis/bindings) to Worker B: * [ wrangler.jsonc ](#tab-panel-7802) * [ wrangler.toml ](#tab-panel-7803) JSONC ``` { "$schema": "./node_modules/wrangler/config-schema.json", "name": "worker_a", "main": "./src/workerA.js", "services": [ { "binding": "WORKER_B", "service": "worker_b" } ] } ``` Explain Code TOML ``` "$schema" = "./node_modules/wrangler/config-schema.json" name = "worker_a" main = "./src/workerA.js" [[services]] binding = "WORKER_B" service = "worker_b" ``` Making it possible for Worker A to call the `add()` method from Worker B: * [ JavaScript ](#tab-panel-7810) * [ TypeScript ](#tab-panel-7811) * [ Python ](#tab-panel-7812) JavaScript ``` export default { async fetch(request, env) { const result = await env.WORKER_B.add(1, 2); return new Response(result); }, }; ``` TypeScript ``` export default { async fetch(request, env) { const result = await env.WORKER_B.add(1, 2); return new Response(result); }, }; ``` Python ``` from workers import WorkerEntrypoint, Response class Default(WorkerEntrypoint): async def fetch(self, request): result = await self.env.WORKER_B.add(1, 2) return Response(f"Result: {result}") ``` The client, in this case Worker A, calls Worker B and tells it to execute a specific procedure using specific arguments that the client provides. This is accomplished with standard JavaScript classes. ## All calls are asynchronous Whether or not the method you are calling was declared asynchronous on the server side, it will behave as such on the client side. You must `await` the result. Note that RPC calls do not actually return `Promise`s, but they return a type that behaves like a `Promise`. The type is a "custom thenable", in that it implements the method `then()`. JavaScript supports awaiting any "thenable" type, so, for the most part, you can treat the return value like a Promise. (We'll see why the type is not actually a Promise a bit later.) ## Structured clonable types, and more Nearly all types that are [Structured Cloneable ↗](https://developer.mozilla.org/en-US/docs/Web/API/Web%5FWorkers%5FAPI/Structured%5Fclone%5Falgorithm#supported%5Ftypes) can be used as a parameter or return value of an RPC method. This includes, most basic "value" types in JavaScript, including objects, arrays, strings and numbers. As an exception to Structured Clone, application-defined classes (or objects with custom prototypes) cannot be passed over RPC, except as described below. The RPC system also supports a number of types that are not Structured Cloneable, including: * Functions, which are replaced by stubs that call back to the sender. * Application-defined classes that extend `RpcTarget`, which are similarly replaced by stubs. * [ReadableStream](https://developers.cloudflare.com/workers/runtime-apis/streams/readablestream/) and [WriteableStream](https://developers.cloudflare.com/workers/runtime-apis/streams/writablestream/), with automatic streaming flow control. * [Request](https://developers.cloudflare.com/workers/runtime-apis/request/) and [Response](https://developers.cloudflare.com/workers/runtime-apis/response/), for conveniently representing HTTP messages. * RPC stubs themselves, even if the stub was received from a third Worker. ## Functions You can send a function over RPC. When you do so, the function is replaced by a "stub". The recipient can call the stub like a function, but doing so makes a new RPC back to the place where the function originated. ### Return functions from RPC methods Consider the following two Workers, connected via a [Service Binding](https://developers.cloudflare.com/workers/runtime-apis/bindings/service-bindings/rpc). The counter service provides the RPC method `newCounter()`, which returns a function: * [ wrangler.jsonc ](#tab-panel-7804) * [ wrangler.toml ](#tab-panel-7805) JSONC ``` { "$schema": "./node_modules/wrangler/config-schema.json", "name": "counter-service", "main": "./src/counterService.js" } ``` TOML ``` "$schema" = "./node_modules/wrangler/config-schema.json" name = "counter-service" main = "./src/counterService.js" ``` * [ JavaScript ](#tab-panel-7813) * [ TypeScript ](#tab-panel-7814) JavaScript ``` import { WorkerEntrypoint } from "cloudflare:workers"; export default class extends WorkerEntrypoint { async fetch() { return new Response("Hello from counter-service"); } async newCounter() { let value = 0; return (increment = 0) => { value += increment; return value; }; } } ``` Explain Code TypeScript ``` import { WorkerEntrypoint } from "cloudflare:workers"; export default class extends WorkerEntrypoint { async fetch() { return new Response("Hello from counter-service"); } async newCounter() { let value = 0; return (increment = 0) => { value += increment; return value; }; } } ``` Explain Code This function can then be called by the client Worker: * [ wrangler.jsonc ](#tab-panel-7806) * [ wrangler.toml ](#tab-panel-7807) JSONC ``` { "$schema": "./node_modules/wrangler/config-schema.json", "name": "client_worker", "main": "./src/clientWorker.js", "services": [ { "binding": "COUNTER_SERVICE", "service": "counter-service" } ] } ``` Explain Code TOML ``` "$schema" = "./node_modules/wrangler/config-schema.json" name = "client_worker" main = "./src/clientWorker.js" [[services]] binding = "COUNTER_SERVICE" service = "counter-service" ``` * [ JavaScript ](#tab-panel-7808) * [ TypeScript ](#tab-panel-7809) JavaScript ``` export default { async fetch(request, env) { using f = await env.COUNTER_SERVICE.newCounter(); await f(2); // returns 2 await f(1); // returns 3 const count = await f(-5); // returns -2 return new Response(count); }, }; ``` Explain Code TypeScript ``` export default { async fetch(request: Request, env: Env) { using f = await env.COUNTER_SERVICE.newCounter(); await f(2); // returns 2 await f(1); // returns 3 const count = await f(-5); // returns -2 return new Response(count); }, }; ``` Explain Code Note Refer to [Explicit Resource Management](https://developers.cloudflare.com/workers/runtime-apis/rpc/lifecycle) to learn more about the `using` declaration shown in the example above. How is this possible? The system is not serializing the function itself. When the function returned by `CounterService` is called, it runs within `CounterService` — even if it is called by another Worker. Under the hood, the caller is not really calling the function itself directly, but calling what is called a "stub". A "stub" is a [Proxy ↗](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global%5FObjects/Proxy) object that allows the client to call the remote service as if it were local, running in the same Worker. Behind the scenes, it calls back to the Worker that implements `CounterService` and asks it to execute the function closure that had been returned earlier. ### Send functions as parameters of RPC methods You can also send a function in the parameters of an RPC. This enables the "server" to call back to the "client", reversing the direction of the relationship. Because of this, the words "client" and "server" can be ambiguous when talking about RPC. The "server" is a Durable Object or WorkerEntrypoint, and the "client" is the Worker that invoked the server via a binding. But, RPCs can flow both ways between the two. When talking about an individual RPC, we recommend instead using the words "caller" and "callee". ## Class Instances To use an instance of a class that you define as a parameter or return value of an RPC method, you must extend the built-in `RpcTarget` class. Consider the following example: * [ wrangler.jsonc ](#tab-panel-7780) * [ wrangler.toml ](#tab-panel-7781) JSONC ``` { "$schema": "./node_modules/wrangler/config-schema.json", "name": "counter", "main": "./src/counter.js" } ``` TOML ``` "$schema" = "./node_modules/wrangler/config-schema.json" name = "counter" main = "./src/counter.js" ``` * [ JavaScript ](#tab-panel-7796) * [ TypeScript ](#tab-panel-7797) JavaScript ``` import { WorkerEntrypoint, RpcTarget } from "cloudflare:workers"; class Counter extends RpcTarget { #value = 0; increment(amount) { this.#value += amount; return this.#value; } get value() { return this.#value; } } export class CounterService extends WorkerEntrypoint { async newCounter() { return new Counter(); } } export default { fetch() { return new Response("ok"); }, }; ``` Explain Code TypeScript ``` import { WorkerEntrypoint, RpcTarget } from "cloudflare:workers"; class Counter extends RpcTarget { #value = 0; increment(amount: number) { this.#value += amount; return this.#value; } get value() { return this.#value; } } export class CounterService extends WorkerEntrypoint { async newCounter() { return new Counter(); } } export default { fetch() { return new Response("ok"); }, }; ``` Explain Code The method `increment` can be called directly by the client, as can the public property `value`: * [ wrangler.jsonc ](#tab-panel-7782) * [ wrangler.toml ](#tab-panel-7783) JSONC ``` { "$schema": "./node_modules/wrangler/config-schema.json", "name": "client-worker", "main": "./src/clientWorker.js", "services": [ { "binding": "COUNTER_SERVICE", "service": "counter", "entrypoint": "CounterService" } ] } ``` Explain Code TOML ``` "$schema" = "./node_modules/wrangler/config-schema.json" name = "client-worker" main = "./src/clientWorker.js" [[services]] binding = "COUNTER_SERVICE" service = "counter" entrypoint = "CounterService" ``` * [ JavaScript ](#tab-panel-7788) * [ TypeScript ](#tab-panel-7789) JavaScript ``` export default { async fetch(request, env) { using counter = await env.COUNTER_SERVICE.newCounter(); await counter.increment(2); // returns 2 await counter.increment(1); // returns 3 await counter.increment(-5); // returns -2 const count = await counter.value; // returns -2 return new Response(count); }, }; ``` Explain Code TypeScript ``` export default { async fetch(request: Request, env: Env) { using counter = await env.COUNTER_SERVICE.newCounter(); await counter.increment(2); // returns 2 await counter.increment(1); // returns 3 await counter.increment(-5); // returns -2 const count = await counter.value; // returns -2 return new Response(count); }, }; ``` Explain Code Note Refer to [Explicit Resource Management](https://developers.cloudflare.com/workers/runtime-apis/rpc/lifecycle) to learn more about the `using` declaration shown in the example above. Classes that extend `RpcTarget` work a lot like functions: the object itself is not serialized, but is instead replaced by a stub. In this case, the stub itself is not callable, but its methods are. Calling any method on the stub actually makes an RPC back to the original object, where it was created. As shown above, you can also access properties of classes. Properties behave like RPC methods that don't take any arguments — you await the property to asynchronously fetch its current value. Note that the act of awaiting the property (which, behind the scenes, calls `.then()` on it) is what causes the property to be fetched. If you do not use `await` when accessing the property, it will not be fetched. Note While it's possible to define a similar interface to the caller using an object that contains many functions, this is less efficient. If you return an object that contains five functions, then you are creating five stubs. If you return a class instance, where the class declares five methods, you are only returning a single stub. Returning a single stub is often more efficient and easier to reason about. Moreover, when returning a plain object (not a class), non-function properties of the object will be transmitted at the time the object itself is transmitted; they cannot be fetched asynchronously on-demand. Note Classes which do not inherit `RpcTarget` cannot be sent over RPC at all. This differs from Structured Clone, which defines application-defined classes as clonable. Why the difference? By default, the Structured Clone algorithm simply ignores an object's class entirely. So, the recipient receives a plain object, containing the original object's instance properties but entirely missing its original type. This behavior is rarely useful in practice, and could be confusing if the developer had intended the class to be treated as an `RpcTarget`. So, Workers RPC has chosen to disallow classes that are not `RpcTarget`s, to avoid any confusion. ### Promise pipelining When you call an RPC method and get back an object, it's common to immediately call a method on the object: * [ JavaScript ](#tab-panel-7784) * [ TypeScript ](#tab-panel-7785) JavaScript ``` // Two round trips. using counter = await env.COUNTER_SERVICE.getCounter(); await counter.increment(); ``` TypeScript ``` // Two round trips. using counter = await env.COUNTER_SERVICE.getCounter(); await counter.increment(); ``` But consider the case where the Worker service that you are calling may be far away across the network, as in the case of [Smart Placement](https://developers.cloudflare.com/workers/configuration/placement/) or [Durable Objects](https://developers.cloudflare.com/durable-objects). The code above makes two round trips, once when calling `getCounter()`, and again when calling `.increment()`. We'd like to avoid this. With most RPC systems, the only way to avoid the problem would be to combine the two calls into a single "batch" call, perhaps called `getCounterAndIncrement()`. However, this makes the interface worse. You wouldn't design a local interface this way. Workers RPC allows a different approach: You can simply omit the first `await`: * [ JavaScript ](#tab-panel-7786) * [ TypeScript ](#tab-panel-7787) JavaScript ``` // Only one round trip! Note the missing `await`. using promiseForCounter = env.COUNTER_SERVICE.getCounter(); await promiseForCounter.increment(); ``` TypeScript ``` // Only one round trip! Note the missing `await`. using promiseForCounter = env.COUNTER_SERVICE.getCounter(); await promiseForCounter.increment(); ``` In this code, `getCounter()` returns a promise for a counter. Normally, the only thing you would do with a promise is `await` it. However, Workers RPC promises are special: they also allow you to initiate speculative calls on the future result of the promise. These calls are sent to the server immediately, without waiting for the initial call to complete. Thus, multiple chained calls can be completed in a single round trip. How does this work? The promise returned by an RPC is not a real JavaScript `Promise`. Instead, it is a custom ["Thenable" ↗](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global%5FObjects/Promise#thenables). It has a `.then()` method like `Promise`, which allows it to be used in all the places where you'd use a normal `Promise`. For instance, you can `await` it. But, in addition to that, an RPC promise also acts like a stub. Calling any method name on the promise forms a speculative call on the promise's eventual result. This is known as "promise pipelining". This works when calling properties of objects returned by RPC methods as well. For example: * [ JavaScript ](#tab-panel-7790) * [ TypeScript ](#tab-panel-7791) JavaScript ``` import { WorkerEntrypoint } from "cloudflare:workers"; export class MyService extends WorkerEntrypoint { async foo() { return { bar: { baz: () => "qux", }, }; } } ``` Explain Code TypeScript ``` import { WorkerEntrypoint } from "cloudflare:workers"; export class MyService extends WorkerEntrypoint { async foo() { return { bar: { baz: () => "qux", }, }; } } ``` Explain Code * [ JavaScript ](#tab-panel-7794) * [ TypeScript ](#tab-panel-7795) JavaScript ``` export default { async fetch(request, env) { using foo = env.MY_SERVICE.foo(); let baz = await foo.bar.baz(); return new Response(baz); }, }; ``` TypeScript ``` export default { async fetch(request, env) { using foo = env.MY_SERVICE.foo(); let baz = await foo.bar.baz(); return new Response(baz); }, }; ``` If the initial RPC ends up throwing an exception, then any pipelined calls will also fail with the same exception ## ReadableStream, WriteableStream, Request and Response You can send and receive [ReadableStream](https://developers.cloudflare.com/workers/runtime-apis/streams/readablestream/), [WriteableStream](https://developers.cloudflare.com/workers/runtime-apis/streams/writablestream/), [Request](https://developers.cloudflare.com/workers/runtime-apis/request/), and [Response](https://developers.cloudflare.com/workers/runtime-apis/response/) using RPC methods. When doing so, bytes in the body are automatically streamed with appropriate flow control. This allows you to send messages over RPC which are larger than [the typical 32 MiB limit](#limitations). Only [byte-oriented streams ↗](https://developer.mozilla.org/en-US/docs/Web/API/Streams%5FAPI/Using%5Freadable%5Fbyte%5Fstreams) (streams with an underlying byte source of `type: "bytes"`) are supported. In all cases, ownership of the stream is transferred to the recipient. The sender can no longer read/write the stream after sending it. If the sender wishes to keep its own copy, it can use the [tee() method of ReadableStream ↗](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream/tee) or the [clone() method of Request or Response ↗](https://developer.mozilla.org/en-US/docs/Web/API/Response/clone). Keep in mind that doing this may force the system to buffer bytes and lose the benefits of flow control. ## Forwarding RPC stubs A stub received over RPC from one Worker can be forwarded over RPC to another Worker. * [ JavaScript ](#tab-panel-7792) * [ TypeScript ](#tab-panel-7793) JavaScript ``` using counter = env.COUNTER_SERVICE.getCounter(); await env.ANOTHER_SERVICE.useCounter(counter); ``` TypeScript ``` using counter = env.COUNTER_SERVICE.getCounter(); await env.ANOTHER_SERVICE.useCounter(counter); ``` Here, three different workers are involved: 1. The calling Worker (we'll call this the "introducer") 2. `COUNTER_SERVICE` 3. `ANOTHER_SERVICE` When `ANOTHER_SERVICE` calls a method on the `counter` that is passed to it, this call will automatically be proxied through the introducer and on to the [RpcTarget](https://developers.cloudflare.com/workers/runtime-apis/rpc/) class implemented by `COUNTER_SERVICE`. In this way, the introducer Worker can connect two Workers that did not otherwise have any ability to form direct connections to each other. Currently, this proxying only lasts until the end of the Workers' execution contexts. A proxy connection cannot be persisted for later use. ## Video Tutorial In this video, we explore how Cloudflare Workers support Remote Procedure Calls (RPC) to simplify communication between Workers. Learn how to implement RPC in your JavaScript applications and build serverless solutions with ease. Whether you're managing microservices or optimizing web architecture, this tutorial will show you how to quickly set up and use Cloudflare Workers for RPC calls. By the end of this video, you'll understand how to call functions between Workers, pass functions as arguments, and implement user authentication with Cloudflare Workers. ## More Details * [ Lifecycle ](https://developers.cloudflare.com/workers/runtime-apis/rpc/lifecycle/) * [ Reserved Methods ](https://developers.cloudflare.com/workers/runtime-apis/rpc/reserved-methods/) * [ Visibility and Security Model ](https://developers.cloudflare.com/workers/runtime-apis/rpc/visibility/) * [ TypeScript ](https://developers.cloudflare.com/workers/runtime-apis/rpc/typescript/) * [ Error handling ](https://developers.cloudflare.com/workers/runtime-apis/rpc/error-handling/) ## Limitations * [Smart Placement](https://developers.cloudflare.com/workers/configuration/placement/) is currently ignored when making RPC calls. If Smart Placement is enabled for Worker A, and Worker B declares a [Service Binding](https://developers.cloudflare.com/workers/runtime-apis/bindings) to it, when Worker B calls Worker A via RPC, Worker A will run locally, on the same machine. * The maximum serialized RPC limit is 32 MiB. Consider using [ReadableStream](https://developers.cloudflare.com/workers/runtime-apis/streams/readablestream/) when returning more data. * [ JavaScript ](#tab-panel-7800) * [ TypeScript ](#tab-panel-7801) JavaScript ``` export class MyService extends WorkerEntrypoint { async foo() { // Although this works, it puts a lot of memory pressure on the isolate. // If possible, streaming the data from its original source is much preferred and would yield better performance. // If you must buffer the data into memory, consider chunking it into smaller pieces if possible. const sizeInBytes = 33 * 1024 * 1024; // 33 MiB const arr = new Uint8Array(sizeInBytes); return new ReadableStream({ start(controller) { controller.enqueue(arr); controller.close(); }, }); } } ``` Explain Code TypeScript ``` export class MyService extends WorkerEntrypoint { async foo() { // Although this works, it puts a lot of memory pressure on the isolate. // If possible, streaming the data from its original source is much preferred and would yield better performance. // If you must buffer the data into memory, consider chunking it into smaller pieces if possible. const sizeInBytes = 33 * 1024 * 1024; // 33 MiB const arr = new Uint8Array(sizeInBytes); return new ReadableStream({ start(controller) { controller.enqueue(arr); controller.close(); }, }); } } ``` Explain Code ```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/runtime-apis/","name":"Runtime APIs"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/runtime-apis/rpc/","name":"Remote-procedure call (RPC)"}}]} ``` --- --- title: Error handling description: How exceptions, stack traces, and logging works with the Workers RPC system. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/runtime-apis/rpc/error-handling.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Error handling ## Exceptions An exception thrown by an RPC method implementation will propagate to the caller. If it is one of the standard JavaScript Error types, the `message` and prototype's `name` will be retained, though the stack trace is not. ### Unsupported error types * If an [AggregateError ↗](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global%5FObjects/AggregateError) is thrown by an RPC method, it is not propagated back to the caller. * The [SuppressedError ↗](https://github.com/tc39/proposal-explicit-resource-management?tab=readme-ov-file#the-suppressederror-error) type from the Explicit Resource Management proposal is not currently implemented or supported in Workers. * Own properties of error objects, such as the [cause ↗](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global%5FObjects/Error/cause) property, are not propagated back to the caller ## Additional properties For some remote exceptions, the runtime may set properties on the propagated exception to provide more information about the error; see [Durable Object error handling](https://developers.cloudflare.com/durable-objects/best-practices/error-handling) for more details. ```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/runtime-apis/","name":"Runtime APIs"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/runtime-apis/rpc/","name":"Remote-procedure call (RPC)"}},{"@type":"ListItem","position":5,"item":{"@id":"/workers/runtime-apis/rpc/error-handling/","name":"Error handling"}}]} ``` --- --- title: Lifecycle description: Memory management, resource management, and the lifecycle of RPC stubs. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/runtime-apis/rpc/lifecycle.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Lifecycle ## Lifetimes, Memory and Resource Management When you call another Worker over RPC using a Service binding, you are using memory in the Worker you are calling. Consider the following example: JavaScript ``` let user = await env.USER_SERVICE.findUser(id); ``` Assume that `findUser()` on the server side returns an object extending `RpcTarget`, thus `user` on the client side ends up being a stub pointing to that remote object. As long as the stub still exists on the client, the corresponding object on the server cannot be garbage collected. But, each isolate has its own garbage collector which cannot see into other isolates. So, in order for the server's isolate to know that the object can be collected, the calling isolate must send it an explicit signal saying so, called "disposing" the stub. In many cases (described below), the system will automatically realize when a stub is no longer needed, and will dispose it automatically. However, for best performance, your code should dispose stubs explicitly when it is done with them. ## Explicit Resource Management To ensure resources are properly disposed of, you should use [Explicit Resource Management ↗](https://github.com/tc39/proposal-explicit-resource-management), a new JavaScript language feature that allows you to explicitly signal when resources can be disposed of. Explicit Resource Management is a Stage 3 TC39 proposal — it is [coming to V8 soon ↗](https://bugs.chromium.org/p/v8/issues/detail?id=13559). Explicit Resource Management adds the following language features: * The [using declaration ↗](https://github.com/tc39/proposal-explicit-resource-management?tab=readme-ov-file#using-declarations) * [Symbol.dispose and Symbol.asyncDispose ↗](https://github.com/tc39/proposal-explicit-resource-management?tab=readme-ov-file#additions-to-symbol) If a variable is declared with `using`, when the variable is no longer in scope, the variable's disposer will be invoked. For example: JavaScript ``` function sendEmail(id, message) { using user = await env.USER_SERVICE.findUser(id); await user.sendEmail(message); // user[Symbol.dispose]() is implicitly called at the end of the scope. } ``` `using` declarations are useful to make sure you can't forget to dispose stubs — even if your code is interrupted by an exception. ### How to use the `using` declaration in your Worker [Wrangler](https://developers.cloudflare.com/workers/wrangler/) v4+ supports the `using` keyword natively. If you are using an earlier version of Wrangler, you will need to manually dispose of resources instead. The following code: JavaScript ``` { using counter = await env.COUNTER_SERVICE.newCounter(); await counter.increment(2); await counter.increment(4); } ``` ...is equivalent to: JavaScript ``` { const counter = await env.COUNTER_SERVICE.newCounter(); try { await counter.increment(2); await counter.increment(4); } finally { counter[Symbol.dispose](); } } ``` ## Automatic disposal and execution contexts The RPC system automatically disposes of stubs in the following cases: ### End of event handler / execution context When an event handler is "done", any stubs created as part of the event are automatically disposed. For example, consider a [fetch() handler](https://developers.cloudflare.com/workers/runtime-apis/handlers/fetch) which handles incoming HTTP events. The handler may make outgoing RPCs as part of handling the event, and those may return stubs. When the final HTTP response is sent, the handler is "done", and all stubs are immediately disposed. More precisely, the event has an "execution context", which begins when the handler is first invoked, and ends when the HTTP response is sent. The execution context may also end early if the client disconnects before receiving a response, or it can be extended past its normal end point by calling [ctx.waitUntil()](https://developers.cloudflare.com/workers/runtime-apis/context). For example, the Worker below does not make use of the `using` declaration, but stubs will be disposed of once the `fetch()` handler returns a response: JavaScript ``` export default { async fetch(request, env, ctx) { let authResult = await env.AUTH_SERVICE.checkCookie( req.headers.get("Cookie"), ); if (!authResult.authorized) { return new Response("Not authorized", { status: 403 }); } let profile = await authResult.user.getProfile(); return new Response(`Hello, ${profile.name}!`); }, }; ``` Explain Code A Worker invoked via RPC also has an execution context. The context begins when an RPC method on a `WorkerEntrypoint` is invoked. If no stubs are passed in the parameters or results of this RPC, the context ends (the event is "done") when the RPC returns. However, if any stubs are passed, then the execution context is implicitly extended until all such stubs are disposed (and all calls made through them have returned). As with HTTP, if the client disconnects, the server's execution context is canceled immediately, regardless of whether stubs still exist. A client that is itself another Worker is considered to have disconnected when its own execution context ends. Again, the context can be extended with [ctx.waitUntil()](https://developers.cloudflare.com/workers/runtime-apis/context). ### Stubs received as parameters in an RPC call When stubs are received in the parameters of an RPC, those stubs are automatically disposed when the call returns. If you wish to keep the stubs longer than that, you must call the `dup()` method on them. ### Disposing RPC objects disposes stubs that are part of that object When an RPC returns any kind of object, that object will have a disposer added by the system. Disposing it will dispose all stubs returned by the call. For instance, if an RPC returns an array of four stubs, the array itself will have a disposer that disposes all four stubs. The only time the value returned by an RPC does not have a disposer is when it is a primitive value, such as a number or string. These types cannot have disposers added to them, but because these types cannot themselves contain stubs, there is no need for a disposer in this case. This means you should almost always store the result of an RPC into a `using` declaration: JavaScript ``` using result = stub.foo(); ``` This way, if the result contains any stubs, they will be disposed of. Even if you don't expect the RPC to return stubs, if it returns any kind of an object, it is a good idea to store it into a `using` declaration. This way, if the RPC is extended in the future to return stubs, your code is ready. If you decide you want to keep a returned stub beyond the scope of the `using` declaration, you can call `dup()` on the stub before the end of the scope. (Remember to explicitly dispose the duplicate later.) ## Disposers and `RpcTarget` classes A class that extends [RpcTarget](https://developers.cloudflare.com/workers/runtime-apis/rpc/) can optionally implement a disposer: JavaScript ``` class Foo extends RpcTarget { [Symbol.dispose]() { // ... } } ``` The RpcTarget's disposer runs after the last stub is disposed. Note that the client-side call to the stub's disposer does not wait for the server-side disposer to be called; the server's disposer is called later on. Because of this, any exceptions thrown by the disposer do not propagate to the client; instead, they are reported as uncaught exceptions. Note that an `RpcTarget`'s disposer must be declared as `Symbol.dispose`. `Symbol.asyncDispose` is not supported. ## The `dup()` method Sometimes, you need to pass a stub to a function which will dispose the stub when it is done, but you also want to keep the stub for later use. To solve this problem, you can "dup" the stub: JavaScript ``` let stub = await env.SOME_SERVICE.getThing(); // Create a duplicate. let stub2 = stub.dup(); // Call some function that will dispose the stub. await func(stub); // stub2 is still valid ``` You can think of `dup()` like the [Unix system call of the same name ↗](https://man7.org/linux/man-pages/man2/dup.2.html): it creates a new handle pointing at the same target, which must be independently closed (disposed). If the instance of the [RpcTarget class](https://developers.cloudflare.com/workers/runtime-apis/rpc/) that the stubs point to has a disposer, the disposer will only be invoked when all duplicates have been disposed. However, this only applies to duplicates that originate from the same stub. If the same instance of `RpcTarget` is passed over RPC multiple times, a new stub is created each time, and these are not considered duplicates of each other. Thus, the disposer will be invoked once for each time the `RpcTarget` was sent. In order to avoid this situation, you can manually create a stub locally, and then pass the stub across RPC multiple times. When passing a stub over RPC, ownership of the stub transfers to the recipient, so you must make a `dup()` for each time you send it: JavaScript ``` import { RpcTarget, RpcStub } from "cloudflare:workers"; class Foo extends RpcTarget { // ... } let obj = new Foo(); let stub = new RpcStub(obj); await rpc1(stub.dup()); // sends a dup of `stub` await rpc2(stub.dup()); // sends another dup of `stub` stub[Symbol.dispose](); // disposes the original stub // obj's disposer will be called when the other two stubs // are disposed remotely. ``` Explain Code ```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/runtime-apis/","name":"Runtime APIs"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/runtime-apis/rpc/","name":"Remote-procedure call (RPC)"}},{"@type":"ListItem","position":5,"item":{"@id":"/workers/runtime-apis/rpc/lifecycle/","name":"Lifecycle"}}]} ``` --- --- title: Reserved Methods description: Reserved methods with special behavior that are treated differently. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/runtime-apis/rpc/reserved-methods.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Reserved Methods Some method names are reserved or have special semantics. ## Special Methods For backwards compatibility, when extending `WorkerEntrypoint` or `DurableObject`, the following method names have special semantics. Note that this does _not_ apply to `RpcTarget`. On `RpcTarget`, these methods work like any other RPC method. ### `fetch()` The `fetch()` method is treated specially — it can only be used to handle an HTTP request — equivalent to the [fetch handler](https://developers.cloudflare.com/workers/runtime-apis/handlers/fetch/). You may implement a `fetch()` method in your class that extends `WorkerEntrypoint` — but it must accept only one parameter of type [Request ↗](https://developer.mozilla.org/en-US/docs/Web/API/Request), and must return an instance of [Response ↗](https://developer.mozilla.org/en-US/docs/Web/API/Response), or a [Promise ↗](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global%5FObjects/Promise) of one. On the client side, `fetch()` called on a service binding or Durable Object stub works like the standard global `fetch()`. That is, the caller may pass one or two parameters to `fetch()`. If the caller does not simply pass a single `Request` object, then a new `Request` is implicitly constructed, passing the parameters to its constructor, and that request is what is actually sent to the server. Some properties of `Request` control the behavior of `fetch()` on the client side and are not actually sent to the server. For example, the property `redirect: "auto"` (which is the default) instructs `fetch()` that if the server returns a redirect response, it should automatically be followed, resulting in an HTTP request to the public internet. Again, this behavior is according to the Fetch API standard. In short, `fetch()` doesn't have RPC semantics, it has Fetch API semantics. ### `connect()` The `connect()` method of the `WorkerEntrypoint` class is reserved for opening a socket-like connection to your Worker. This is currently not implemented or supported — though you can [open a TCP socket from a Worker](https://developers.cloudflare.com/workers/runtime-apis/tcp-sockets/) or connect directly to databases over a TCP socket with [Hyperdrive](https://developers.cloudflare.com/hyperdrive/get-started/). ## Disallowed Method Names The following method (or property) names may not be used as RPC methods on any RPC type (including `WorkerEntrypoint`, `DurableObject`, and `RpcTarget`): * `dup`: This is reserved for duplicating a stub. Refer to the [RPC Lifecycle](https://developers.cloudflare.com/workers/runtime-apis/rpc/lifecycle) docs to learn more about `dup()`. * `constructor`: This name has special meaning for JavaScript classes. It is not intended to be called as a method, so it is not allowed over RPC. The following methods are disallowed only on `WorkerEntrypoint` and `DurableObject`, but allowed on `RpcTarget`. These methods have historically had special meaning to Durable Objects, where they are used to handle certain system-generated events. * `alarm` * `webSocketMessage` * `webSocketClose` * `webSocketError` ```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/runtime-apis/","name":"Runtime APIs"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/runtime-apis/rpc/","name":"Remote-procedure call (RPC)"}},{"@type":"ListItem","position":5,"item":{"@id":"/workers/runtime-apis/rpc/reserved-methods/","name":"Reserved Methods"}}]} ``` --- --- title: TypeScript description: How TypeScript types for your Worker or Durable Object's RPC methods are generated and exposed to clients image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/runtime-apis/rpc/typescript.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # TypeScript Running [wrangler types](https://developers.cloudflare.com/workers/languages/typescript/#generate-types) generates runtime types including the `Service` and `DurableObjectNamespace` types, each of which accepts a single type parameter for the [WorkerEntrypoint](https://developers.cloudflare.com/workers/runtime-apis/bindings/service-bindings/rpc) or [DurableObject](https://developers.cloudflare.com/durable-objects/best-practices/create-durable-object-stubs-and-send-requests/#call-rpc-methods) types. Using higher-order types, we automatically generate client-side stub types (e.g., forcing all methods to be async). [wrangler types](https://developers.cloudflare.com/workers/languages/typescript/#generate-types) also generates types for the `env` object. You can pass in the path to the config files of the Worker or Durable Object being called so that the generated types include the type parameters for the `Service` and `DurableObjectNamespace` types. For example, if your client Worker had bindings to a Worker in `../sum-worker/` and a Durable Object in `../counter/`, you should generate types for the client Worker's `env` by running: npm yarn pnpm ``` npx wrangler types -c ./client/wrangler.jsonc -c ../sum-worker/wrangler.jsonc -c ../counter/wrangler.jsonc ``` ``` yarn wrangler types -c ./client/wrangler.jsonc -c ../sum-worker/wrangler.jsonc -c ../counter/wrangler.jsonc ``` ``` pnpm wrangler types -c ./client/wrangler.jsonc -c ../sum-worker/wrangler.jsonc -c ../counter/wrangler.jsonc ``` This will produce a `worker-configuration.d.ts` file that includes: worker-configuration.d.ts ``` interface Env { SUM_SERVICE: Service; COUNTER_OBJECT: DurableObjectNamespace< import("../counter/src/index").Counter >; } ``` Now types for RPC method like the `env.SUM_SERVICE.sum` method will be exposed to the client Worker. src/index.ts ``` export default { async fetch(req, env, ctx): Promise { const result = await env.SUM_SERVICE.sum(1, 2); return new Response(result.toString()); }, } satisfies ExportedHandler; ``` ```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/runtime-apis/","name":"Runtime APIs"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/runtime-apis/rpc/","name":"Remote-procedure call (RPC)"}},{"@type":"ListItem","position":5,"item":{"@id":"/workers/runtime-apis/rpc/typescript/","name":"TypeScript"}}]} ``` --- --- title: Visibility and Security Model description: Which properties are and are not exposed to clients that communicate with your Worker or Durable Object via RPC image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/runtime-apis/rpc/visibility.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Visibility and Security Model ## Security Model The Workers RPC system is intended to allow safe communications between Workers that do not trust each other. The system does not allow either side of an RPC session to access arbitrary objects on the other side, much less invoke arbitrary code. Instead, each side can only invoke the objects and functions for which they have explicitly received stubs via previous calls. This security model is commonly known as Object Capabilities, or Capability-Based Security. Workers RPC is built on [Cap'n Proto RPC ↗](https://capnproto.org/rpc.html), which in turn is based on CapTP, the object transport protocol used by the [distributed programming language E ↗](https://www.crockford.com/ec/etut.html). ## Visibility of Methods and Properties ### Private properties [Private properties ↗](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/Private%5Fproperties) of classes are not directly exposed over RPC. ### Class instance properties When you send an instance of an application-defined class, the recipient can only access methods and properties declared on the class, not properties of the instance. For example: JavaScript ``` class Foo extends RpcTarget { constructor() { super(); // i CANNOT be accessed over RPC this.i = 0; // funcProp CANNOT be called over RPC this.funcProp = () => {} } // value CAN be accessed over RPC get value() { return this.i; } // method CAN be called over RPC method() {} } ``` Explain Code This behavior is intentional — it is intended to protect you from accidentally exposing private class internals. Generally, instance properties should be declared private, [by prefixing them with # ↗](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/Private%5Fproperties). However, private properties are a relatively new feature of JavaScript, and are not yet widely used in the ecosystem. Since the RPC interface between two of your Workers may be a security boundary, we need to be extra-careful, so instance properties are always private when communicating between Workers using RPC, whether or not they have the `#` prefix. You can always declare an explicit getter at the class level if you wish to expose the property, as shown above. These visibility rules apply only to objects that extend `RpcTarget`, `WorkerEntrypoint`, or `DurableObject`, and do not apply to plain objects. Plain objects are passed "by value", sending all of their "own" properties. ### "Own" properties of functions When you pass a function over RPC, the caller can access the "own" properties of the function object itself. JavaScript ``` someRpcMethod() { let func = () => {}; func.prop = 123; // `prop` is visible over RPC return func; } ``` Such properties on a function are accessed asynchronously, like class properties of an RpcTarget. But, unlike the `RpcTarget` example above, the function's instance properties that are accessible to the caller. In practice, properties are rarely added to functions. ```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/runtime-apis/","name":"Runtime APIs"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/runtime-apis/rpc/","name":"Remote-procedure call (RPC)"}},{"@type":"ListItem","position":5,"item":{"@id":"/workers/runtime-apis/rpc/visibility/","name":"Visibility and Security Model"}}]} ``` --- --- title: Scheduler description: Use the scheduler.wait() API to delay execution in Workers. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/runtime-apis/scheduler.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Scheduler ## Background The `scheduler` global provides task scheduling APIs based on the [WICG Scheduling APIs proposal ↗](https://github.com/WICG/scheduling-apis). Workers currently implement the `scheduler.wait()` method. `scheduler.wait()` returns a Promise that resolves after a given number of milliseconds. It is an `await`\-able alternative to `setTimeout()` that does not require a callback. Like other [timers in Workers](https://developers.cloudflare.com/workers/runtime-apis/web-standards/#timers), `scheduler.wait()` does not advance during CPU execution when deployed to Cloudflare. This is a [security measure to mitigate against Spectre attacks](https://developers.cloudflare.com/workers/reference/security-model/#step-1-disallow-timers-and-multi-threading). In local development, timers advance regardless of whether I/O occurs. ## Syntax JavaScript ``` await scheduler.wait(delay); await scheduler.wait(delay, options); ``` ## Parameters * `delay` number * The number of milliseconds to wait before the returned Promise resolves. * `options` object optional * Optional configuration for the wait operation. * `signal` AbortSignal optional * An [AbortSignal](https://developers.cloudflare.com/workers/runtime-apis/web-standards/#abortcontroller-and-abortsignal) that cancels the wait. When the signal is aborted, the returned Promise rejects with an `AbortError`. ## Return value A `Promise` that resolves after `delay` milliseconds. If an `AbortSignal` is provided and aborted before the delay elapses, the Promise rejects with an `AbortError`. ## Examples ### Basic delay Use `scheduler.wait()` to pause execution for a specified duration. * [ JavaScript ](#tab-panel-7818) * [ TypeScript ](#tab-panel-7819) JavaScript ``` export default { async fetch(request) { // Wait for 1 second await scheduler.wait(1000); return new Response("Delayed response"); }, }; ``` TypeScript ``` export default { async fetch(request): Promise { // Wait for 1 second await scheduler.wait(1000); return new Response("Delayed response"); }, } satisfies ExportedHandler; ``` ### Retry with exponential backoff Use `scheduler.wait()` to implement a delay between retry attempts. This example uses exponential backoff with jitter. * [ JavaScript ](#tab-panel-7822) * [ TypeScript ](#tab-panel-7823) JavaScript ``` async function fetchWithRetry(url, maxAttempts = 3) { const baseBackoffMs = 100; const maxBackoffMs = 10000; for (let attempt = 0; attempt < maxAttempts; attempt++) { try { return await fetch(url); } catch (err) { if (attempt + 1 >= maxAttempts) { throw err; } const backoffMs = Math.min( maxBackoffMs, baseBackoffMs * Math.random() * Math.pow(2, attempt), ); await scheduler.wait(backoffMs); } } throw new Error("unreachable"); } export default { async fetch(request) { const response = await fetchWithRetry("https://example.com/api"); return new Response(response.body, response); }, }; ``` Explain Code TypeScript ``` async function fetchWithRetry(url: string, maxAttempts = 3): Promise { const baseBackoffMs = 100; const maxBackoffMs = 10000; for (let attempt = 0; attempt < maxAttempts; attempt++) { try { return await fetch(url); } catch (err) { if (attempt + 1 >= maxAttempts) { throw err; } const backoffMs = Math.min( maxBackoffMs, baseBackoffMs * Math.random() * Math.pow(2, attempt), ); await scheduler.wait(backoffMs); } } throw new Error("unreachable"); } export default { async fetch(request): Promise { const response = await fetchWithRetry("https://example.com/api"); return new Response(response.body, response); }, } satisfies ExportedHandler; ``` Explain Code ### Cancel with AbortSignal Use an [AbortController](https://developers.cloudflare.com/workers/runtime-apis/web-standards/#abortcontroller-and-abortsignal) to cancel a pending wait. * [ JavaScript ](#tab-panel-7820) * [ TypeScript ](#tab-panel-7821) JavaScript ``` export default { async fetch(request) { const controller = new AbortController(); // Cancel the wait after 500ms setTimeout(() => controller.abort(), 500); try { await scheduler.wait(5000, { signal: controller.signal }); return new Response("Wait completed"); } catch (err) { if (err instanceof DOMException && err.name === "AbortError") { return new Response("Wait was cancelled", { status: 408 }); } throw err; } }, }; ``` Explain Code TypeScript ``` export default { async fetch(request): Promise { const controller = new AbortController(); // Cancel the wait after 500ms setTimeout(() => controller.abort(), 500); try { await scheduler.wait(5000, { signal: controller.signal }); return new Response("Wait completed"); } catch (err) { if (err instanceof DOMException && err.name === "AbortError") { return new Response("Wait was cancelled", { status: 408 }); } throw err; } }, } satisfies ExportedHandler; ``` Explain Code ## Related resources * [Timers](https://developers.cloudflare.com/workers/runtime-apis/web-standards/#timers) — `setTimeout()` and `setInterval()` APIs * [Performance and timers](https://developers.cloudflare.com/workers/runtime-apis/performance/) — `performance.now()` and timer security behavior * [AbortController and AbortSignal](https://developers.cloudflare.com/workers/runtime-apis/web-standards/#abortcontroller-and-abortsignal) — cancel asynchronous operations * [WICG Scheduling APIs proposal ↗](https://github.com/WICG/scheduling-apis) — the specification this API is based on ```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/runtime-apis/","name":"Runtime APIs"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/runtime-apis/scheduler/","name":"Scheduler"}}]} ``` --- --- title: Streams description: A web standard API that allows JavaScript to programmatically access and process streams of data. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/runtime-apis/streams/index.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Streams The [Streams API ↗](https://developer.mozilla.org/en-US/docs/Web/API/Streams%5FAPI) is a web standard API that allows JavaScript to programmatically access and process streams of data. * [ ReadableStream ](https://developers.cloudflare.com/workers/runtime-apis/streams/readablestream/) * [ ReadableStream BYOBReader ](https://developers.cloudflare.com/workers/runtime-apis/streams/readablestreambyobreader/) * [ ReadableStream DefaultReader ](https://developers.cloudflare.com/workers/runtime-apis/streams/readablestreamdefaultreader/) * [ TransformStream ](https://developers.cloudflare.com/workers/runtime-apis/streams/transformstream/) * [ WritableStream ](https://developers.cloudflare.com/workers/runtime-apis/streams/writablestream/) * [ WritableStream DefaultWriter ](https://developers.cloudflare.com/workers/runtime-apis/streams/writablestreamdefaultwriter/) Use the Streams API to avoid buffering large requests or responses in memory. This enables you to parse extremely large request or response bodies within a Worker's 128 MB memory limit. This is faster than buffering the entire payload into memory, as your Worker can start processing data incrementally, and allows your Worker to handle multi-gigabyte payloads or files within its memory limits. Workers do not need to prepare an entire response body before returning a `Response`. You can use a [ReadableStream](https://developers.cloudflare.com/workers/runtime-apis/streams/readablestream/) to stream a response body after sending the response status line and headers. Note By default, Cloudflare Workers is capable of streaming responses using the [Streams APIs ↗](https://developer.mozilla.org/en-US/docs/Web/API/Streams%5FAPI). To maintain the streaming behavior, you should only modify the response body using the methods in the Streams APIs. If your Worker only forwards subrequest responses to the client verbatim without reading their body text, then its body handling is already optimal and you do not have to use these APIs. The worker can create a `Response` object using a `ReadableStream` as the body. Any data provided through the`ReadableStream` will be streamed to the client as it becomes available. * [ Module Worker ](#tab-panel-7824) * [ Service Worker ](#tab-panel-7825) JavaScript ``` export default { async fetch(request, env, ctx) { // Fetch from origin server. const response = await fetch(request); // ... and deliver our Response while that’s running. return new Response(response.body, response); }, }; ``` Service Workers are deprecated Service Workers are deprecated, but still supported. We recommend using [Module Workers](https://developers.cloudflare.com/workers/reference/migrate-to-module-workers/) instead. New features may not be supported for Service Workers. JavaScript ``` addEventListener("fetch", (event) => { event.respondWith(fetchAndStream(event.request)); }); async function fetchAndStream(request) { // Fetch from origin server. const response = await fetch(request); // ... and deliver our Response while that’s running. return new Response(readable.body, response); } ``` Explain Code A [TransformStream](https://developers.cloudflare.com/workers/runtime-apis/streams/transformstream/) and the [ReadableStream.pipeTo()](https://developers.cloudflare.com/workers/runtime-apis/streams/readablestream/#methods) method can be used to modify the response body as it is being streamed: * [ Module Worker ](#tab-panel-7826) * [ Service Worker ](#tab-panel-7827) JavaScript ``` export default { async fetch(request, env, ctx) { // Fetch from origin server. const response = await fetch(request); const { readable, writable } = new TransformStream({ transform(chunk, controller) { controller.enqueue(modifyChunkSomehow(chunk)); }, }); // Start pumping the body. NOTE: No await! response.body.pipeTo(writable); // ... and deliver our Response while that’s running. return new Response(readable, response); }, }; ``` Explain Code Service Workers are deprecated Service Workers are deprecated, but still supported. We recommend using [Module Workers](https://developers.cloudflare.com/workers/reference/migrate-to-module-workers/) instead. New features may not be supported for Service Workers. JavaScript ``` addEventListener("fetch", (event) => { event.respondWith(fetchAndStream(event.request)); }); async function fetchAndStream(request) { // Fetch from origin server. const response = await fetch(request); const { readable, writable } = new TransformStream({ transform(chunk, controller) { controller.enqueue(modifyChunkSomehow(chunk)); }, }); // Start pumping the body. NOTE: No await! response.body.pipeTo(writable); // ... and deliver our Response while that’s running. return new Response(readable, response); } ``` Explain Code This example calls `response.body.pipeTo(writable)` but does not `await` it. This is so it does not block the forward progress of the remainder of the `fetchAndStream()` function. It continues to run asynchronously until the response is complete or the client disconnects. The runtime can continue running a function (`response.body.pipeTo(writable)`) after a response is returned to the client. This example pumps the subrequest response body to the final response body. However, you can use more complicated logic, such as adding a prefix or a suffix to the body or to process it somehow. --- ## Common issues Warning The Streams API is only available inside of the [Request context](https://developers.cloudflare.com/workers/runtime-apis/request/), inside the `fetch` event listener callback. --- ## Related resources * [Stream large JSON](https://developers.cloudflare.com/workers/examples/streaming-json/) \- Parse and transform large JSON request and response bodies * [MDN's Streams API documentation ↗](https://developer.mozilla.org/en-US/docs/Web/API/Streams%5FAPI) * [Streams API spec ↗](https://streams.spec.whatwg.org/) * Write your Worker code in [ES modules syntax](https://developers.cloudflare.com/workers/reference/migrate-to-module-workers/) for an optimized experience. ```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/runtime-apis/","name":"Runtime APIs"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/runtime-apis/streams/","name":"Streams"}}]} ``` --- --- title: ReadableStream description: A ReadableStream is returned by the readable property inside TransformStream. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/runtime-apis/streams/readablestream.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # ReadableStream ## Background A `ReadableStream` is returned by the `readable` property inside [TransformStream](https://developers.cloudflare.com/workers/runtime-apis/streams/transformstream/). ## Properties * `locked` boolean * A Boolean value that indicates if the readable stream is locked to a reader. ## Methods * `pipeTo(destinationWritableStream, optionsPipeToOptions)` : Promise * Pipes the readable stream to a given writable stream `destination` and returns a promise that is fulfilled when the `write` operation succeeds or rejects it if the operation fails. * `getReader(optionsObject)` : ReadableStreamDefaultReader * Gets an instance of `ReadableStreamDefaultReader` and locks the `ReadableStream` to that reader instance. This method accepts an object argument indicating options. The only supported option is `mode`, which can be set to `byob` to create a [ReadableStreamBYOBReader](https://developers.cloudflare.com/workers/runtime-apis/streams/readablestreambyobreader/), as shown here: JavaScript ``` let reader = readable.getReader({ mode: 'byob' }); ``` ### `PipeToOptions` * `preventClose` bool * When `true`, closure of the source `ReadableStream` will not cause the destination `WritableStream` to be closed. * `preventAbort` bool * When `true`, errors in the source `ReadableStream` will no longer abort the destination `WritableStream`. `pipeTo` will return a rejected promise with the error from the source or any error that occurred while aborting the destination. --- ## Related resources * [Streams](https://developers.cloudflare.com/workers/runtime-apis/streams/) * [Readable streams in the WHATWG Streams API specification ↗](https://streams.spec.whatwg.org/#rs-model) * [MDN’s ReadableStream documentation ↗](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream) ```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/runtime-apis/","name":"Runtime APIs"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/runtime-apis/streams/","name":"Streams"}},{"@type":"ListItem","position":5,"item":{"@id":"/workers/runtime-apis/streams/readablestream/","name":"ReadableStream"}}]} ``` --- --- title: ReadableStream BYOBReader description: BYOB is an abbreviation of bring your own buffer. A ReadableStreamBYOBReader allows reading into a developer-supplied buffer, thus minimizing copies. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/runtime-apis/streams/readablestreambyobreader.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # ReadableStream BYOBReader ## Background `BYOB` is an abbreviation of bring your own buffer. A `ReadableStreamBYOBReader` allows reading into a developer-supplied buffer, thus minimizing copies. An instance of `ReadableStreamBYOBReader` is functionally identical to [ReadableStreamDefaultReader](https://developers.cloudflare.com/workers/runtime-apis/streams/readablestreamdefaultreader/) with the exception of the `read` method. A `ReadableStreamBYOBReader` is not instantiated via its constructor. Rather, it is retrieved from a [ReadableStream](https://developers.cloudflare.com/workers/runtime-apis/streams/readablestream/): JavaScript ``` const { readable, writable } = new TransformStream(); const reader = readable.getReader({ mode: 'byob' }); ``` --- ## Methods * `read(bufferArrayBufferView)` : Promise * Returns a promise with the next available chunk of data read into a passed-in buffer. * `readAtLeast(minBytes, bufferArrayBufferView)` : Promise * Returns a promise with the next available chunk of data read into a passed-in buffer. The promise will not resolve until at least `minBytes` bytes have been read. However, fewer than `minBytes` bytes may be returned if the end of the stream is reached or the underlying stream is closed. Specifically: * If `minBytes` or more bytes are available, the promise resolves with `{ value: , done: false }`. * If the stream ends after some bytes have been read but fewer than `minBytes`, the promise resolves with the partial data: `{ value: , done: false }`. The next call to `read` or `readAtLeast` will then return `{ value: undefined, done: true }`. * If the stream ends with zero bytes available (that is, the stream is already at EOF), the promise resolves with `{ value: , done: true }`. * If the stream errors, the promise rejects. * `minBytes` must be at least 1, and must not exceed the byte length of `bufferArrayBufferView`, or the promise rejects with a `TypeError`. --- ## Common issues Warning `read` provides no control over the minimum number of bytes that should be read into the buffer. Even if you allocate a 1 MiB buffer, the kernel is perfectly within its rights to fulfill this read with a single byte, whether or not an EOF immediately follows. In practice, the Workers team has found that `read` typically fills only 1% of the provided buffer. `readAtLeast` is a non-standard extension to the Streams API which allows users to specify that at least `minBytes` bytes must be read into the buffer before resolving the read. If the stream ends before `minBytes` bytes are available, the partial data that was read is still returned rather than throwing an error — refer to the [readAtLeast method documentation above](#methods) for the full details. --- ## Related resources * [Streams](https://developers.cloudflare.com/workers/runtime-apis/streams/) * [Background about BYOB readers in the Streams API WHATWG specification ↗](https://streams.spec.whatwg.org/#byob-readers) ```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/runtime-apis/","name":"Runtime APIs"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/runtime-apis/streams/","name":"Streams"}},{"@type":"ListItem","position":5,"item":{"@id":"/workers/runtime-apis/streams/readablestreambyobreader/","name":"ReadableStream BYOBReader"}}]} ``` --- --- title: ReadableStream DefaultReader description: A reader is used when you want to read from a ReadableStream, rather than piping its output to a WritableStream. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/runtime-apis/streams/readablestreamdefaultreader.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # ReadableStream DefaultReader ## Background A reader is used when you want to read from a [ReadableStream](https://developers.cloudflare.com/workers/runtime-apis/streams/readablestream/), rather than piping its output to a [WritableStream](https://developers.cloudflare.com/workers/runtime-apis/streams/writablestream/). A `ReadableStreamDefaultReader` is not instantiated via its constructor. Rather, it is retrieved from a [ReadableStream](https://developers.cloudflare.com/workers/runtime-apis/streams/readablestream/): JavaScript ``` const { readable, writable } = new TransformStream(); const reader = readable.getReader(); ``` --- ## Properties * `reader.closed` : Promise * A promise indicating if the reader is closed. The promise is fulfilled when the reader stream closes and is rejected if there is an error in the stream. ## Methods * `read()` : Promise * A promise that returns the next available chunk of data being passed through the reader queue. * `cancel(reasonstringoptional)` : void * Cancels the stream. `reason` is an optional human-readable string indicating the reason for cancellation. `reason` will be passed to the underlying source’s cancel algorithm -- if this readable stream is one side of a [TransformStream](https://developers.cloudflare.com/workers/runtime-apis/streams/transformstream/), then its cancel algorithm causes the transform’s writable side to become errored with `reason`. Warning Any data not yet read is lost. * `releaseLock()` : void * Releases the lock on the readable stream. A lock cannot be released if the reader has pending read operations. A `TypeError` is thrown and the reader remains locked. --- ## Related resources * [Streams](https://developers.cloudflare.com/workers/runtime-apis/streams/) * [Readable streams in the WHATWG Streams API specification ↗](https://streams.spec.whatwg.org/#rs-model) ```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/runtime-apis/","name":"Runtime APIs"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/runtime-apis/streams/","name":"Streams"}},{"@type":"ListItem","position":5,"item":{"@id":"/workers/runtime-apis/streams/readablestreamdefaultreader/","name":"ReadableStream DefaultReader"}}]} ``` --- --- title: TransformStream description: A transform stream consists of a pair of streams: a writable stream, known as its writable side, and a readable stream, known as its readable side. Writes to the writable side result in new data being made available for reading from the readable side. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/runtime-apis/streams/transformstream.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # TransformStream ## Background A transform stream consists of a pair of streams: a writable stream, known as its writable side, and a readable stream, known as its readable side. Writes to the writable side result in new data being made available for reading from the readable side. Workers currently only implements an identity transform stream, a type of transform stream which forwards all chunks written to its writable side to its readable side, without any changes. --- ## Constructor JavaScript ``` let { readable, writable } = new TransformStream(); ``` * `TransformStream()` TransformStream * Returns a new identity transform stream. ## Properties * `readable` ReadableStream * An instance of a `ReadableStream`. * `writable` WritableStream * An instance of a `WritableStream`. --- ## `IdentityTransformStream` The current implementation of `TransformStream` in the Workers platform is not current compliant with the [Streams Standard ↗](https://streams.spec.whatwg.org/#transform-stream) and we will soon be making changes to the implementation to make it conform with the specification. In preparation for doing so, we have introduced the `IdentityTransformStream` class that implements behavior identical to the current `TransformStream` class. This type of stream forwards all chunks of byte data (in the form of `TypedArray`s) written to its writable side to its readable side, without any changes. The `IdentityTransformStream` readable side supports [bring your own buffer (BYOB) reads ↗](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStreamBYOBReader). ### Constructor JavaScript ``` let { readable, writable } = new IdentityTransformStream(); ``` * `IdentityTransformStream()` IdentityTransformStream * Returns a new identity transform stream. ### Properties * `readable` ReadableStream * An instance of a `ReadableStream`. * `writable` WritableStream * An instance of a `WritableStream`. --- ## `FixedLengthStream` The `FixedLengthStream` is a specialization of `IdentityTransformStream` that limits the total number of bytes that the stream will passthrough. It is useful primarily because, when using `FixedLengthStream` to produce either a `Response` or `Request`, the fixed length of the stream will be used as the `Content-Length` header value as opposed to use chunked encoding when using any other type of stream. An error will occur if too many, or too few bytes are written through the stream. ### Constructor JavaScript ``` let { readable, writable } = new FixedLengthStream(1000); ``` * `FixedLengthStream(length)` FixedLengthStream * Returns a new identity transform stream. * `length` maybe a `number` or `bigint` with a maximum value of `2^53 - 1`. ### Properties * `readable` ReadableStream * An instance of a `ReadableStream`. * `writable` WritableStream * An instance of a `WritableStream`. --- ## Related resources * [Streams](https://developers.cloudflare.com/workers/runtime-apis/streams/) * [Transform Streams in the WHATWG Streams API specification ↗](https://streams.spec.whatwg.org/#transform-stream) ```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/runtime-apis/","name":"Runtime APIs"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/runtime-apis/streams/","name":"Streams"}},{"@type":"ListItem","position":5,"item":{"@id":"/workers/runtime-apis/streams/transformstream/","name":"TransformStream"}}]} ``` --- --- title: WritableStream description: A WritableStream is the writable property of a TransformStream. On the Workers platform, WritableStream cannot be directly created using the WritableStream constructor. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/runtime-apis/streams/writablestream.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # WritableStream ## Background A `WritableStream` is the `writable` property of a [TransformStream](https://developers.cloudflare.com/workers/runtime-apis/streams/transformstream/). On the Workers platform, `WritableStream` cannot be directly created using the `WritableStream` constructor. A typical way to write to a `WritableStream` is to pipe a [ReadableStream](https://developers.cloudflare.com/workers/runtime-apis/streams/readablestream/) to it. JavaScript ``` readableStream .pipeTo(writableStream) .then(() => console.log('All data successfully written!')) .catch(e => console.error('Something went wrong!', e)); ``` To write to a `WritableStream` directly, you must use its writer. JavaScript ``` const writer = writableStream.getWriter(); writer.write(data); ``` Refer to the [WritableStreamDefaultWriter](https://developers.cloudflare.com/workers/runtime-apis/streams/writablestreamdefaultwriter/) documentation for further detail. ## Properties * `locked` boolean * A Boolean value to indicate if the writable stream is locked to a writer. ## Methods * `abort(reasonstringoptional)` : Promise * Aborts the stream. This method returns a promise that fulfills with a response `undefined`. `reason` is an optional human-readable string indicating the reason for cancellation. `reason` will be passed to the underlying sink’s abort algorithm. If this writable stream is one side of a [TransformStream](https://developers.cloudflare.com/workers/runtime-apis/streams/transformstream/), then its abort algorithm causes the transform’s readable side to become errored with `reason`. Warning Any data not yet written is lost upon abort. * `getWriter()` : WritableStreamDefaultWriter * Gets an instance of `WritableStreamDefaultWriter` and locks the `WritableStream` to that writer instance. --- ## Related resources * [Streams](https://developers.cloudflare.com/workers/runtime-apis/streams/) * [Writable streams in the WHATWG Streams API specification ↗](https://streams.spec.whatwg.org/#ws-model) ```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/runtime-apis/","name":"Runtime APIs"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/runtime-apis/streams/","name":"Streams"}},{"@type":"ListItem","position":5,"item":{"@id":"/workers/runtime-apis/streams/writablestream/","name":"WritableStream"}}]} ``` --- --- title: WritableStream DefaultWriter description: A writer is used when you want to write directly to a WritableStream, rather than piping data to it from a ReadableStream. For example: image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/runtime-apis/streams/writablestreamdefaultwriter.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # WritableStream DefaultWriter ## Background A writer is used when you want to write directly to a [WritableStream](https://developers.cloudflare.com/workers/runtime-apis/streams/writablestream/), rather than piping data to it from a [ReadableStream](https://developers.cloudflare.com/workers/runtime-apis/streams/readablestream/). For example: JavaScript ``` function writeArrayToStream(array, writableStream) { const writer = writableStream.getWriter(); array.forEach(chunk => writer.write(chunk).catch(() => {})); return writer.close(); } writeArrayToStream([1, 2, 3, 4, 5], writableStream) .then(() => console.log('All done!')) .catch(e => console.error('Error with the stream: ' + e)); ``` Explain Code ## Properties * `writer.desiredSize` int * The size needed to fill the stream’s internal queue, as an integer. Always returns 1, 0 (if the stream is closed), or `null` (if the stream has errors). * `writer.closed` Promise * A promise that indicates if the writer is closed. The promise is fulfilled when the writer stream is closed and rejected if there is an error in the stream. ## Methods * `abort(reasonstringoptional)` : Promise * Aborts the stream. This method returns a promise that fulfills with a response `undefined`. `reason` is an optional human-readable string indicating the reason for cancellation. `reason` will be passed to the underlying sink’s abort algorithm. If this writable stream is one side of a [TransformStream](https://developers.cloudflare.com/workers/runtime-apis/streams/transformstream/), then its abort algorithm causes the transform’s readable side to become errored with `reason`. Warning Any data not yet written is lost upon abort. * `close()` : Promise * Attempts to close the writer. Remaining writes finish processing before the writer is closed. This method returns a promise fulfilled with `undefined` if the writer successfully closes and processes the remaining writes, or rejected on any error. * `releaseLock()` : void * Releases the writer’s lock on the stream. Once released, the writer is no longer active. You can call this method before all pending `write(chunk)` calls are resolved. This allows you to queue a `write` operation, release the lock, and begin piping into the writable stream from another source, as shown in the example below. JavaScript ``` let writer = writable.getWriter(); // Write a preamble. writer.write(new TextEncoder().encode('foo bar')); // While that’s still writing, pipe the rest of the body from somewhere else. writer.releaseLock(); await someResponse.body.pipeTo(writable); ``` * `write(chunkany)` : Promise * Writes a chunk of data to the writer and returns a promise that resolves if the operation succeeds. * The underlying stream may accept fewer kinds of type than `any`, it will throw an exception when encountering an unexpected type. --- ## Related resources * [Streams](https://developers.cloudflare.com/workers/runtime-apis/streams/) * [Writable streams in the WHATWG Streams API specification ↗](https://streams.spec.whatwg.org/#ws-model) ```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/runtime-apis/","name":"Runtime APIs"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/runtime-apis/streams/","name":"Streams"}},{"@type":"ListItem","position":5,"item":{"@id":"/workers/runtime-apis/streams/writablestreamdefaultwriter/","name":"WritableStream DefaultWriter"}}]} ``` --- --- title: TCP sockets description: Use the `connect()` API to create outbound TCP connections from Workers. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/runtime-apis/tcp-sockets.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # TCP sockets The Workers runtime provides the `connect()` API for creating outbound [TCP connections ↗](https://www.cloudflare.com/learning/ddos/glossary/tcp-ip/) from Workers. Many application-layer protocols are built on top of the Transmission Control Protocol (TCP). These application-layer protocols, including SSH, MQTT, SMTP, FTP, IRC, and most database wire protocols including MySQL, PostgreSQL, MongoDB, require an underlying TCP socket API in order to work. Note Connecting to a PostgreSQL database? You should use [Hyperdrive](https://developers.cloudflare.com/hyperdrive/), which provides the `connect()` API with built-in connection pooling and query caching. Note TCP Workers outbound connections are sourced from a prefix that is not part of [list of IP ranges ↗](https://www.cloudflare.com/ips/). ## `connect()` The `connect()` function returns a TCP socket, with both a [readable](https://developers.cloudflare.com/workers/runtime-apis/streams/readablestream/) and [writable](https://developers.cloudflare.com/workers/runtime-apis/streams/writablestream/) stream of data. This allows you to read and write data on an ongoing basis, as long as the connection remains open. `connect()` is provided as a [Runtime API](https://developers.cloudflare.com/workers/runtime-apis/), and is accessed by importing the `connect` function from `cloudflare:sockets`. This process is similar to how one imports built-in modules in Node.js. Refer to the following codeblock for an example of creating a TCP socket, writing to it, and returning the readable side of the socket as a response: TypeScript ``` import { connect } from 'cloudflare:sockets'; export default { async fetch(req): Promise { const gopherAddr = { hostname: "gopher.floodgap.com", port: 70 }; const url = new URL(req.url); try { const socket = connect(gopherAddr); const writer = socket.writable.getWriter() const encoder = new TextEncoder(); const encoded = encoder.encode(url.pathname + "\r\n"); await writer.write(encoded); await writer.close(); return new Response(socket.readable, { headers: { "Content-Type": "text/plain" } }); } catch (error) { return new Response("Socket connection failed: " + error, { status: 500 }); } } } satisfies ExportedHandler; ``` Explain Code * `connect(address: SocketAddress | string, options?: optional SocketOptions)` : `Socket` * `connect()` accepts either a URL string or [SocketAddress](https://developers.cloudflare.com/workers/runtime-apis/tcp-sockets/#socketaddress) to define the hostname and port number to connect to, and an optional configuration object, [SocketOptions](https://developers.cloudflare.com/workers/runtime-apis/tcp-sockets/#socketoptions). It returns an instance of a [Socket](https://developers.cloudflare.com/workers/runtime-apis/tcp-sockets/#socket). ### `SocketAddress` * `hostname` string * The hostname to connect to. Example: `cloudflare.com`. * `port` number * The port number to connect to. Example: `5432`. ### `SocketOptions` * `secureTransport` "off" | "on" | "starttls" — Defaults to `off` * Specifies whether or not to use [TLS ↗](https://www.cloudflare.com/learning/ssl/transport-layer-security-tls/) when creating the TCP socket. * `off` — Do not use TLS. * `on` — Use TLS. * `starttls` — Do not use TLS initially, but allow the socket to be upgraded to use TLS by calling [startTls()](https://developers.cloudflare.com/workers/runtime-apis/tcp-sockets/#opportunistic-tls-starttls). * `allowHalfOpen` boolean — Defaults to `false` * Defines whether the writable side of the TCP socket will automatically close on end-of-file (EOF). When set to `false`, the writable side of the TCP socket will automatically close on EOF. When set to `true`, the writable side of the TCP socket will remain open on EOF. * This option is similar to that offered by the Node.js [net module ↗](https://nodejs.org/api/net.html) and allows interoperability with code which utilizes it. ### `SocketInfo` * `remoteAddress` string | null * The address of the remote peer the socket is connected to. May not always be set. * `localAddress` string | null * The address of the local network endpoint for this socket. May not always be set. ### `Socket` * `readable` : ReadableStream * Returns the readable side of the TCP socket. * `writable` : WritableStream * Returns the writable side of the TCP socket. * The `WritableStream` returned only accepts chunks of `Uint8Array` or its views. * `opened` `Promise` * This promise is resolved when the socket connection is established and is rejected if the socket encounters an error. * `closed` `Promise` * This promise is resolved when the socket is closed and is rejected if the socket encounters an error. * `close()` `Promise` * Closes the TCP socket. Both the readable and writable streams are forcibly closed. * `startTls()` : Socket * Upgrades an insecure socket to a secure one that uses TLS, returning a new [Socket](https://developers.cloudflare.com/workers/runtime-apis/tcp-sockets#socket). Note that in order to call `startTls()`, you must set [secureTransport](https://developers.cloudflare.com/workers/runtime-apis/tcp-sockets/#socketoptions) to `starttls` when initially calling `connect()` to create the socket. ## Opportunistic TLS (StartTLS) Many TCP-based systems, including databases and email servers, require that clients use opportunistic TLS (otherwise known as [StartTLS ↗](https://en.wikipedia.org/wiki/Opportunistic%5FTLS)) when connecting. In this pattern, the client first creates an insecure TCP socket, without TLS, and then upgrades it to a secure TCP socket, that uses TLS. The `connect()` API simplifies this by providing a method, `startTls()`, which returns a new `Socket` instance that uses TLS: TypeScript ``` import { connect } from "cloudflare:sockets" const address = { hostname: "example-postgres-db.com", port: 5432 }; const socket = connect(address, { secureTransport: "starttls" }); const secureSocket = socket.startTls(); ``` * `startTls()` can only be called if `secureTransport` is set to `starttls` when creating the initial TCP socket. * Once `startTls()` is called, the initial socket is closed and can no longer be read from or written to. In the example above, anytime after `startTls()` is called, you would use the newly created `secureSocket`. Any existing readers and writers based off the original socket will no longer work. You must create new readers and writers from the newly created `secureSocket`. * `startTls()` should only be called once on an existing socket. ## Handle errors To handle errors when creating a new TCP socket, reading from a socket, or writing to a socket, wrap these calls inside [try...catch ↗](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/try...catch) statement blocks. The following example opens a connection to Google.com, initiates a HTTP request, and returns the response. If this fails and throws an exception, it returns a [500](https://developers.cloudflare.com/support/troubleshooting/http-status-codes/cloudflare-5xx-errors/error-500/) response: TypeScript ``` import { connect } from 'cloudflare:sockets'; const connectionUrl = { hostname: "google.com", port: 80 }; export interface Env { } export default { async fetch(req, env, ctx): Promise { try { const socket = connect(connectionUrl); const writer = socket.writable.getWriter(); const encoder = new TextEncoder(); const encoded = encoder.encode("GET / HTTP/1.0\r\n\r\n"); await writer.write(encoded); await writer.close(); return new Response(socket.readable, { headers: { "Content-Type": "text/plain" } }); } catch (error) { return new Response(`Socket connection failed: ${error}`, { status: 500 }); } } } satisfies ExportedHandler; ``` Explain Code ## Close TCP connections You can close a TCP connection by calling `close()` on the socket. This will close both the readable and writable sides of the socket. TypeScript ``` import { connect } from "cloudflare:sockets" const socket = connect({ hostname: "my-url.com", port: 70 }); const reader = socket.readable.getReader(); socket.close(); // After close() is called, you can no longer read from the readable side of the socket const reader = socket.readable.getReader(); // This fails ``` ## Considerations * Outbound TCP sockets to [Cloudflare IP ranges ↗](https://www.cloudflare.com/ips/) are blocked. * TCP sockets cannot be created in global scope and shared across requests. You should always create TCP sockets within a handler (ex: [fetch()](https://developers.cloudflare.com/workers/get-started/guide/#3-write-code), [scheduled()](https://developers.cloudflare.com/workers/runtime-apis/handlers/scheduled/), [queue()](https://developers.cloudflare.com/queues/configuration/javascript-apis/#consumer)) or [alarm()](https://developers.cloudflare.com/durable-objects/api/alarms/). * Each open TCP socket counts towards the maximum number of [open connections](https://developers.cloudflare.com/workers/platform/limits/#simultaneous-open-connections) that can be simultaneously open. * By default, Workers cannot create outbound TCP connections on port `25` to send email to SMTP mail servers. [Cloudflare Email Workers](https://developers.cloudflare.com/email-routing/email-workers/) provides APIs to process and forward email. * Support for handling inbound TCP connections is [coming soon ↗](https://blog.cloudflare.com/workers-tcp-socket-api-connect-databases/). Currently, it is not possible to make an inbound TCP connection to your Worker, for example, by using the `CONNECT` HTTP method. ## Troubleshooting Review descriptions of common error messages you may see when working with TCP Sockets, what the error messages mean, and how to solve them. ### `proxy request failed, cannot connect to the specified address` Your socket is connecting to an address that was disallowed. Examples of a disallowed address include Cloudflare IPs, `localhost`, and private network IPs. If you need to connect to addresses on port `80` or `443` to make HTTP requests, use [fetch](https://developers.cloudflare.com/workers/runtime-apis/fetch/). ### `TCP Loop detected` Your socket is connecting back to the Worker that initiated the outbound connection. In other words, the Worker is connecting back to itself. This is currently not supported. ### `Connections to port 25 are prohibited` Your socket is connecting to an address on port `25`. This is usually the port used for SMTP mail servers. Workers cannot create outbound connections on port `25`. Consider using [Cloudflare Email Workers](https://developers.cloudflare.com/email-routing/email-workers/) instead. ```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/runtime-apis/","name":"Runtime APIs"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/runtime-apis/tcp-sockets/","name":"TCP sockets"}}]} ``` --- --- title: Web Crypto description: A set of low-level functions for common cryptographic tasks. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/runtime-apis/web-crypto.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Web Crypto ## Background The Web Crypto API provides a set of low-level functions for common cryptographic tasks. The Workers runtime implements the full surface of this API, but with some differences in the [supported algorithms](#supported-algorithms) compared to those implemented in most browsers. Performing cryptographic operations using the Web Crypto API is significantly faster than performing them purely in JavaScript. If you want to perform CPU-intensive cryptographic operations, you should consider using the Web Crypto API. The Web Crypto API is implemented through the `SubtleCrypto` interface, accessible via the global `crypto.subtle` binding. A simple example of calculating a digest (also known as a hash) is: JavaScript ``` const myText = new TextEncoder().encode('Hello world!'); const myDigest = await crypto.subtle.digest( { name: 'SHA-256', }, myText // The data you want to hash as an ArrayBuffer ); console.log(new Uint8Array(myDigest)); ``` Explain Code Some common uses include [signing requests](https://developers.cloudflare.com/workers/examples/signing-requests/). Warning The Web Crypto API differs significantly from the [Node.js Crypto API](https://developers.cloudflare.com/workers/runtime-apis/nodejs/crypto/). If you are working with code that relies on the Node.js Crypto API, you can use it by enabling the [nodejs\_compat compatibility flag](https://developers.cloudflare.com/workers/runtime-apis/nodejs/). --- ## Constructors * `crypto.DigestStream(algorithm)` DigestStream * A non-standard extension to the `crypto` API that supports generating a hash digest from streaming data. The `DigestStream` itself is a [WritableStream](https://developers.cloudflare.com/workers/runtime-apis/streams/writablestream/) that does not retain the data written into it. Instead, it generates a hash digest automatically when the flow of data has ended. ### Parameters * `algorithm`string | object * Describes the algorithm to be used, including any required parameters, in [an algorithm-specific format ↗](https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/digest#Syntax). ### Usage * [ JavaScript ](#tab-panel-7828) * [ TypeScript ](#tab-panel-7829) JavaScript ``` export default { async fetch(req) { // Fetch from origin const res = await fetch(req); // We need to read the body twice so we `tee` it (get two instances) const [bodyOne, bodyTwo] = res.body.tee(); // Make a new response so we can set the headers (responses from `fetch` are immutable) const newRes = new Response(bodyOne, res); // Create a SHA-256 digest stream and pipe the body into it const digestStream = new crypto.DigestStream("SHA-256"); bodyTwo.pipeTo(digestStream); // Get the final result const digest = await digestStream.digest; // Turn it into a hex string const hexString = [...new Uint8Array(digest)] .map(b => b.toString(16).padStart(2, '0')) .join('') // Set a header with the SHA-256 hash and return the response newRes.headers.set("x-content-digest", `SHA-256=${hexString}`); return newRes; } } ``` Explain Code TypeScript ``` export default { async fetch(req): Promise { // Fetch from origin const res = await fetch(req); // We need to read the body twice so we `tee` it (get two instances) const [bodyOne, bodyTwo] = res.body.tee(); // Make a new response so we can set the headers (responses from `fetch` are immutable) const newRes = new Response(bodyOne, res); // Create a SHA-256 digest stream and pipe the body into it const digestStream = new crypto.DigestStream("SHA-256"); bodyTwo.pipeTo(digestStream); // Get the final result const digest = await digestStream.digest; // Turn it into a hex string const hexString = [...new Uint8Array(digest)] .map(b => b.toString(16).padStart(2, '0')) .join('') // Set a header with the SHA-256 hash and return the response newRes.headers.set("x-content-digest", `SHA-256=${hexString}`); return newRes; } } satisfies ExportedHandler; ``` Explain Code ## Methods * `crypto.randomUUID()` : string * Generates a new random (version 4) UUID as defined in [RFC 4122 ↗](https://www.rfc-editor.org/rfc/rfc4122.txt). * `crypto.getRandomValues(bufferArrayBufferView)` : ArrayBufferView * Fills the passed `ArrayBufferView` with cryptographically sound random values and returns the `buffer`. ### Parameters * `buffer`ArrayBufferView * Must be an Int8Array | Uint8Array | Uint8ClampedArray | Int16Array | Uint16Array | Int32Array | Uint32Array | BigInt64Array | BigUint64Array. ## SubtleCrypto Methods These methods are all accessed via [crypto.subtle ↗](https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto#Methods), which is also documented in detail on MDN. ### encrypt * `encrypt(algorithm, key, data)` : Promise * Returns a Promise that fulfills with the encrypted data corresponding to the clear text, algorithm, and key given as parameters. #### Parameters * `algorithm`object * Describes the algorithm to be used, including any required parameters, in [an algorithm-specific format ↗](https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/encrypt#Syntax). * `key`CryptoKey * `data`BufferSource ### decrypt * `decrypt(algorithm, key, data)` : Promise * Returns a Promise that fulfills with the clear data corresponding to the ciphertext, algorithm, and key given as parameters. #### Parameters * `algorithm`object * Describes the algorithm to be used, including any required parameters, in [an algorithm-specific format ↗](https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/decrypt#Syntax). * `key`CryptoKey * `data`BufferSource ### sign * `sign(algorithm, key, data)` : Promise * Returns a Promise that fulfills with the signature corresponding to the text, algorithm, and key given as parameters. #### Parameters * `algorithm`string | object * Describes the algorithm to be used, including any required parameters, in [an algorithm-specific format ↗](https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/sign#Syntax). * `key`CryptoKey * `data`ArrayBuffer ### verify * `verify(algorithm, key, signature, data)` : Promise * Returns a Promise that fulfills with a Boolean value indicating if the signature given as a parameter matches the text, algorithm, and key that are also given as parameters. #### Parameters * `algorithm`string | object * Describes the algorithm to be used, including any required parameters, in [an algorithm-specific format ↗](https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/verify#Syntax). * `key`CryptoKey * `signature`ArrayBuffer * `data`ArrayBuffer ### digest * `digest(algorithm, data)` : Promise * Returns a Promise that fulfills with a digest generated from the algorithm and text given as parameters. #### Parameters * `algorithm`string | object * Describes the algorithm to be used, including any required parameters, in [an algorithm-specific format ↗](https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/digest#Syntax). * `data`ArrayBuffer ### generateKey * `generateKey(algorithm, extractable, keyUsages)` : Promise | Promise * Returns a Promise that fulfills with a newly-generated `CryptoKey`, for symmetrical algorithms, or a `CryptoKeyPair`, containing two newly generated keys, for asymmetrical algorithms. For example, to generate a new AES-GCM key: JavaScript ``` let keyPair = await crypto.subtle.generateKey( { name: 'AES-GCM', length: 256, }, true, ['encrypt', 'decrypt'] ); ``` #### Parameters * `algorithm`object * Describes the algorithm to be used, including any required parameters, in [an algorithm-specific format ↗](https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/generateKey#Syntax). * `extractable`bool * `keyUsages`Array * An Array of strings indicating the [possible usages of the new key ↗](https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/generateKey#Syntax). ### deriveKey * `deriveKey(algorithm, baseKey, derivedKeyAlgorithm, extractable, keyUsages)` : Promise * Returns a Promise that fulfills with a newly generated `CryptoKey` derived from the base key and specific algorithm given as parameters. #### Parameters * `algorithm`object * Describes the algorithm to be used, including any required parameters, in [an algorithm-specific format ↗](https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/deriveKey#Syntax). * `baseKeyCryptoKey` * `derivedKeyAlgorithmobject` * Defines the algorithm the derived key will be used for in [an algorithm-specific format ↗](https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/deriveKey#Syntax). * `extractablebool` * `keyUsagesArray` * An Array of strings indicating the [possible usages of the new key ↗](https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/deriveKey#Syntax) ### deriveBits * `deriveBits(algorithm, baseKey, length)` : Promise * Returns a Promise that fulfills with a newly generated buffer of pseudo-random bits derived from the base key and specific algorithm given as parameters. It returns a Promise which will be fulfilled with an `ArrayBuffer` containing the derived bits. This method is very similar to `deriveKey()`, except that `deriveKey()` returns a `CryptoKey` object rather than an `ArrayBuffer`. Essentially, `deriveKey()` is composed of `deriveBits()` followed by `importKey()`. #### Parameters * `algorithm`object * Describes the algorithm to be used, including any required parameters, in [an algorithm-specific format ↗](https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/deriveBits#Syntax). * `baseKey`CryptoKey * `length`int * Length of the bit string to derive. ### importKey * `importKey(format, keyData, algorithm, extractable, keyUsages)` : Promise * Transform a key from some external, portable format into a `CryptoKey` for use with the Web Crypto API. #### Parameters * `format`string * Describes [the format of the key to be imported ↗](https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/importKey#Syntax). * `keyData`ArrayBuffer * `algorithm`object * Describes the algorithm to be used, including any required parameters, in [an algorithm-specific format ↗](https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/importKey#Syntax). * `extractable`bool * `keyUsages`Array * An Array of strings indicating the [possible usages of the new key ↗](https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/importKey#Syntax) ### exportKey * `exportKey(formatstring, keyCryptoKey)` : Promise * Transform a `CryptoKey` into a portable format, if the `CryptoKey` is `extractable`. #### Parameters * `format`string * Describes the [format in which the key will be exported ↗](https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/exportKey#Syntax). * `key`CryptoKey ### wrapKey * `wrapKey(format, key, wrappingKey, wrapAlgo)` : Promise * Transform a `CryptoKey` into a portable format, and then encrypt it with another key. This renders the `CryptoKey` suitable for storage or transmission in untrusted environments. #### Parameters * `format`string * Describes the [format in which the key will be exported ↗](https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/wrapKey#Syntax) before being encrypted. * `key`CryptoKey * `wrappingKey`CryptoKey * `wrapAlgo`object * Describes the algorithm to be used to encrypt the exported key, including any required parameters, in [an algorithm-specific format ↗](https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/wrapKey#Syntax). ### unwrapKey * `unwrapKey(format, key, unwrappingKey, unwrapAlgo, unwrappedKeyAlgo, extractable, keyUsages)` : Promise * Transform a key that was wrapped by `wrapKey()` back into a `CryptoKey`. #### Parameters * `format`string * Described the [data format of the key to be unwrapped ↗](https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/unwrapKey#Syntax). * `key`CryptoKey * `unwrappingKey`CryptoKey * `unwrapAlgo`object * Describes the algorithm that was used to encrypt the wrapped key, [in an algorithm-specific format ↗](https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/unwrapKey#Syntax). * `unwrappedKeyAlgo`object * Describes the key to be unwrapped, [in an algorithm-specific format ↗](https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/unwrapKey#Syntax). * `extractable`bool * `keyUsages`Array * An Array of strings indicating the [possible usages of the new key ↗](https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/unwrapKey#Syntax) ### timingSafeEqual * `timingSafeEqual(a, b)` : bool * Compare two buffers in a way that is resistant to timing attacks. This is a non-standard extension to the Web Crypto API. #### Parameters * `a`ArrayBuffer | TypedArray * `b`ArrayBuffer | TypedArray ### Supported algorithms Workers implements all operations of the [WebCrypto standard ↗](https://www.w3.org/TR/WebCryptoAPI/), as shown in the following table. A checkmark (✓) indicates that this feature is believed to be fully supported according to the spec. An x (✘) indicates that this feature is part of the specification but not implemented. If a feature only implements the operation partially, details are listed. | Algorithm | sign()verify() | encrypt()decrypt() | digest() | deriveBits()deriveKey() | generateKey() | wrapKey()unwrapKey() | exportKey() | importKey() | | ---------------------------- | -------------- | ------------------ | -------- | ----------------------- | ------------- | -------------------- | ----------- | ----------- | | RSASSA PKCS1 v1.5 | ✓ | ✓ | ✓ | ✓ | | | | | | RSA PSS | ✓ | ✓ | ✓ | ✓ | | | | | | RSA OAEP | ✓ | ✓ | ✓ | ✓ | ✓ | | | | | ECDSA | ✓ | ✓ | ✓ | ✓ | | | | | | ECDH | ✓ | ✓ | ✓ | ✓ | | | | | | Ed25519[1](#footnote-1) | ✓ | ✓ | ✓ | ✓ | | | | | | X25519[1](#footnote-1) | ✓ | ✓ | ✓ | ✓ | | | | | | NODE ED25519[2](#footnote-2) | ✓ | ✓ | ✓ | ✓ | | | | | | AES CTR | ✓ | ✓ | ✓ | ✓ | ✓ | | | | | AES CBC | ✓ | ✓ | ✓ | ✓ | ✓ | | | | | AES GCM | ✓ | ✓ | ✓ | ✓ | ✓ | | | | | AES KW | ✓ | ✓ | ✓ | ✓ | | | | | | HMAC | ✓ | ✓ | ✓ | ✓ | | | | | | SHA 1 | ✓ | | | | | | | | | SHA 256 | ✓ | | | | | | | | | SHA 384 | ✓ | | | | | | | | | SHA 512 | ✓ | | | | | | | | | MD5[3](#footnote-3) | ✓ | | | | | | | | | HKDF | ✓ | ✓ | | | | | | | | PBKDF2 | ✓ | ✓ | | | | | | | **Footnotes:** 1. Algorithms as specified in the [Secure Curves API ↗](https://wicg.github.io/webcrypto-secure-curves). 2. Legacy non-standard EdDSA is supported for the Ed25519 curve in addition to the Secure Curves version. Since this algorithm is non-standard, note the following while using it: * Use `NODE-ED25519` as the algorithm and `namedCurve` parameters. * Unlike NodeJS, Cloudflare will not support raw import of private keys. * The algorithm implementation may change over time. While Cloudflare cannot guarantee it at this time, Cloudflare will strive to maintain backward compatibility and compatibility with NodeJS's behavior. Any notable compatibility notes will be communicated in release notes and via this developer documentation. 3. MD5 is not part of the WebCrypto standard but is supported in Cloudflare Workers for interacting with legacy systems that require MD5\. MD5 is considered a weak algorithm. Do not rely upon MD5 for security. --- ## Related resources * [SubtleCrypto documentation on MDN ↗](https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto) * [SubtleCrypto documentation as part of the W3C Web Crypto API specification ↗](https://www.w3.org/TR/WebCryptoAPI//#subtlecrypto-interface) * [Example: signing requests](https://developers.cloudflare.com/workers/examples/signing-requests/) ```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/runtime-apis/","name":"Runtime APIs"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/runtime-apis/web-crypto/","name":"Web Crypto"}}]} ``` --- --- title: Web standards description: Standardized APIs for use by Workers running on Cloudflare's global network. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/runtime-apis/web-standards.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Web standards ## JavaScript standards The Cloudflare Workers runtime is [built on top of the V8 JavaScript and WebAssembly engine](https://developers.cloudflare.com/workers/reference/how-workers-works/). The Workers runtime is updated at least once a week, to at least the version of V8 that is currently used by Google Chrome's stable release. This means you can safely use the latest JavaScript features, with no need for transpilers. All of the [standard built-in objects ↗](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference) supported by the current Google Chrome stable release are supported, with a few notable exceptions: * For security reasons, the following are not allowed: * `eval()` * `new Function` * [WebAssembly.compile ↗](https://developer.mozilla.org/en-US/docs/WebAssembly/JavaScript%5Finterface/compile%5Fstatic) * [WebAssembly.compileStreaming ↗](https://developer.mozilla.org/en-US/docs/WebAssembly/JavaScript%5Finterface/compileStreaming%5Fstatic) * `WebAssembly.instantiate` with a [buffer parameter ↗](https://developer.mozilla.org/en-US/docs/WebAssembly/JavaScript%5Finterface/instantiate%5Fstatic#primary%5Foverload%5F%E2%80%94%5Ftaking%5Fwasm%5Fbinary%5Fcode) * [WebAssembly.instantiateStreaming ↗](https://developer.mozilla.org/en-US/docs/WebAssembly/JavaScript%5Finterface/instantiateStreaming%5Fstatic) * `Date.now()` returns the time of the last I/O; it does not advance during code execution. --- ## Web standards and global APIs The following methods are available per the [Worker Global Scope ↗](https://developer.mozilla.org/en-US/docs/Web/API/WorkerGlobalScope): ### Base64 utility methods * atob() * Decodes a string of data which has been encoded using base-64 encoding. * btoa() * Creates a base-64 encoded ASCII string from a string of binary data. ### Timers * setInterval() * Schedules a function to execute every time a given number of milliseconds elapses. * clearInterval() * Cancels the repeated execution set using [setInterval() ↗](https://developer.mozilla.org/en-US/docs/Web/API/setInterval). * setTimeout() * Schedules a function to execute in a given amount of time. * clearTimeout() * Cancels the delayed execution set using [setTimeout() ↗](https://developer.mozilla.org/en-US/docs/Web/API/setTimeout). * [scheduler.wait()](https://developers.cloudflare.com/workers/runtime-apis/scheduler/) * Returns a Promise that resolves after a given number of milliseconds. An `await`\-able alternative to `setTimeout()`. Note Timers are only available inside of [the Request Context](https://developers.cloudflare.com/workers/runtime-apis/request/#the-request-context). ### `performance.timeOrigin` and `performance.now()` * performance.timeOrigin * Returns the high resolution time origin. Workers uses the UNIX epoch as the time origin, meaning that `performance.timeOrigin` will always return `0`. * performance.now() * Returns a `DOMHighResTimeStamp` representing the number of milliseconds elapsed since `performance.timeOrigin`. Note that Workers intentionally reduces the precision of `performance.now()` such that it returns the time of the last I/O and does not advance during code execution. Effectively, because of this, and because `performance.timeOrigin` is always, `0`, `performance.now()` will always equal `Date.now()`, yielding a consistent view of the passage of time within a Worker. ### `EventTarget` and `Event` The [EventTarget ↗](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget) and [Event ↗](https://developer.mozilla.org/en-US/docs/Web/API/Event) API allow objects to publish and subscribe to events. ### `AbortController` and `AbortSignal` The [AbortController ↗](https://developer.mozilla.org/en-US/docs/Web/API/AbortController) and [AbortSignal ↗](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) APIs provide a common model for canceling asynchronous operations. ### Fetch global * fetch() * Starts the process of fetching a resource from the network. Refer to [Fetch API](https://developers.cloudflare.com/workers/runtime-apis/fetch/). Note The Fetch API is only available inside of [the Request Context](https://developers.cloudflare.com/workers/runtime-apis/request/#the-request-context). --- ## Encoding API Both `TextEncoder` and `TextDecoder` support UTF-8 encoding/decoding. [Refer to the MDN documentation for more information ↗](https://developer.mozilla.org/en-US/docs/Web/API/Encoding%5FAPI). The [TextEncoderStream ↗](https://developer.mozilla.org/en-US/docs/Web/API/TextEncoderStream) and [TextDecoderStream ↗](https://developer.mozilla.org/en-US/docs/Web/API/TextDecoderStream) classes are also available. --- ## URL API The URL API supports URLs conforming to HTTP and HTTPS schemes. [Refer to the MDN documentation for more information ↗](https://developer.mozilla.org/en-US/docs/Web/API/URL) Note The default URL class behavior differs from the URL Spec documented above. A new spec-compliant implementation of the URL class can be enabled using the `url_standard` [compatibility flag](https://developers.cloudflare.com/workers/configuration/compatibility-flags/). --- ## Compression Streams The `CompressionStream` and `DecompressionStream` classes support the deflate, deflate-raw and gzip compression methods. [Refer to the MDN documentation for more information ↗](https://developer.mozilla.org/en-US/docs/Web/API/Compression%5FStreams%5FAPI) --- ## URLPattern API The `URLPattern` API provides a mechanism for matching URLs based on a convenient pattern syntax. [Refer to the MDN documentation for more information ↗](https://developer.mozilla.org/en-US/docs/Web/API/URLPattern). --- ## `Intl` The `Intl` API allows you to format dates, times, numbers, and more to the format that is used by a provided locale (language and region). [Refer to the MDN documentation for more information ↗](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global%5FObjects/Intl). --- ## `navigator.userAgent` When the [global\_navigator](https://developers.cloudflare.com/workers/configuration/compatibility-flags/#global-navigator) compatibility flag is set, the [navigator.userAgent ↗](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/userAgent) property is available with the value `'Cloudflare-Workers'`. This can be used, for example, to reliably determine that code is running within the Workers environment. ## Unhandled promise rejections The [unhandledrejection ↗](https://developer.mozilla.org/en-US/docs/Web/API/Window/unhandledrejection%5Fevent) event is emitted by the global scope when a JavaScript promise is rejected without a rejection handler attached. The [rejectionhandled ↗](https://developer.mozilla.org/en-US/docs/Web/API/Window/rejectionhandled%5Fevent) event is emitted by the global scope when a JavaScript promise rejection is handled late (after a rejection handler is attached to the promise after an `unhandledrejection` event has already been emitted). worker.js ``` addEventListener("unhandledrejection", (event) => { console.log(event.promise); // The promise that was rejected. console.log(event.reason); // The value or Error with which the promise was rejected. }); addEventListener("rejectionhandled", (event) => { console.log(event.promise); // The promise that was rejected. console.log(event.reason); // The value or Error with which the promise was rejected. }); ``` --- ## `navigator.sendBeacon(url[, data])` When the [global\_navigator](https://developers.cloudflare.com/workers/configuration/compatibility-flags/#global-navigator) compatibility flag is set, the [navigator.sendBeacon(...) ↗](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/sendBeacon) API is available to send an HTTP `POST` request containing a small amount of data to a web server. This API is intended as a means of transmitting analytics or diagnostics information asynchronously on a best-effort basis. For example, you can replace: JavaScript ``` const promise = fetch("https://example.com", { method: "POST", body: "hello world", }); ctx.waitUntil(promise); ``` with `navigator.sendBeacon(...)`: JavaScript ``` navigator.sendBeacon("https://example.com", "hello world"); ``` ## The Web File System Access API When the `enable_web_file_system` compatibility flag is set, Workers supports the [Web File System Access API ↗](https://developer.mozilla.org/en-US/docs/Web/API/File%5FSystem%5FAccess%5FAPI), which allows you to read and write files and directories to a virtual file system within the Worker environment. This API provides access to the same in-memory virtual file system as the [node:fs module](https://developers.cloudflare.com/workers/runtime-apis/nodejs/fs/) but does not require Node.js compatibility to be enabled. JavaScript ``` const root = await navigator.storage.getDirectory(); export default { async fetch(request) { const fileHandle = await root.getFileHandle("hello.txt", { create: true }); const writable = await fileHandle.createWritable(); await writable.write("Hello, world!"); await writable.close(); const file = await fileHandle.getFile(); const contents = await file.text(); return new Response(contents, { status: 200 }); }, }; ``` Explain Code Please refer to the [MDN documentation ↗](https://developer.mozilla.org/en-US/docs/Web/API/File%5FSystem%5FAccess%5FAPI) for more information on using this API, and to the [node:fs documentation](https://developers.cloudflare.com/workers/runtime-apis/nodejs/fs/) for details on the virtual file system structure and limitations. ```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/runtime-apis/","name":"Runtime APIs"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/runtime-apis/web-standards/","name":"Web standards"}}]} ``` --- --- title: WebAssembly (Wasm) description: Execute code written in a language other than JavaScript or write an entire Cloudflare Worker in Rust. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/runtime-apis/webassembly/index.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # WebAssembly (Wasm) [WebAssembly ↗](https://webassembly.org/) (abbreviated Wasm) allows you to compile languages like [Rust](https://developers.cloudflare.com/workers/languages/rust/), Go, or C to a binary format that can run in a wide variety of environments, including [web browsers ↗](https://developer.mozilla.org/en-US/docs/WebAssembly#browser%5Fcompatibility), Cloudflare Workers, and other WebAssembly runtimes. You can use WebAssembly to: * Execute code written in a language other than JavaScript, via `WebAssembly.instantiate()`. Note `WebAssembly.instantiate()` only supports pre-compiled modules as documented in the [web-standards documentation](https://developers.cloudflare.com/workers/runtime-apis/web-standards/#javascript-standards). * Write an entire Cloudflare Worker in Rust, using bindings that make Workers' JavaScript APIs available directly from your Rust code. Most programming languages can be compiled to Wasm, although support varies across languages and compilers. Guides are available for the following languages: * [ Wasm in JavaScript ](https://developers.cloudflare.com/workers/runtime-apis/webassembly/javascript/) ## Supported proposals WebAssembly is a rapidly evolving set of standards, with [many proposed APIs ↗](https://webassembly.org/roadmap/) which are in various stages of development. In general, Workers supports the same set of features that are available in Google Chrome. ### SIMD SIMD is supported on Workers. For more information on using SIMD in WebAssembly, refer to [Fast, parallel applications with WebAssembly SIMD ↗](https://v8.dev/features/simd). ### Threading Threading is not possible in Workers. Each Worker runs in a single thread, and the [Web Worker ↗](https://developer.mozilla.org/en-US/docs/Web/API/Web%5FWorkers%5FAPI) API is not supported. ## Binary size Compiling to WebAssembly often requires including additional runtime dependencies. As a result, Workers that use WebAssembly are typically larger than an equivalent Worker written in JavaScript. The larger your Worker is, the longer it may take your Worker to start. Refer to [Worker startup time ↗](https://developers.cloudflare.com/workers/platform/limits/#worker-startup-time) for more information. We recommend using tools like [wasm-opt ↗](https://github.com/brson/wasm-opt-rs) to optimize the size of your Wasm binary. ## WebAssembly System Interface (WASI) The [WebAssembly System Interface ↗](https://wasi.dev/) (abbreviated WASI) is a modular system interface for WebAssembly that standardizes a set of underlying system calls for networking, file system access, and more. Applications can depend on the WebAssembly System Interface to behave identically across host environments and operating systems. WASI is an earlier and more rapidly evolving set of standards than Wasm. WASI support is experimental on Cloudflare Workers, with only some syscalls implemented. Refer to our [open source implementation of WASI ↗](https://github.com/cloudflare/workers-wasi), and [blog post about WASI on Workers ↗](https://blog.cloudflare.com/announcing-wasi-on-workers/) demonstrating its use. ### Resources on WebAssembly * [Serverless Rust with Cloudflare Workers ↗](https://blog.cloudflare.com/cloudflare-workers-as-a-serverless-rust-platform/) * [WebAssembly on Cloudflare Workers ↗](https://blog.cloudflare.com/webassembly-on-cloudflare-workers/) ```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/runtime-apis/","name":"Runtime APIs"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/runtime-apis/webassembly/","name":"WebAssembly (Wasm)"}}]} ``` --- --- title: Wasm in JavaScript description: Wasm can be used from within a Worker written in JavaScript or TypeScript by importing a Wasm module, and instantiating an instance of this module using WebAssembly.instantiate(). This can be used to accelerate computationally intensive operations which do not involve significant I/O. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/runtime-apis/webassembly/javascript.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Wasm in JavaScript Wasm can be used from within a Worker written in JavaScript or TypeScript by importing a Wasm module, and instantiating an instance of this module using [WebAssembly.instantiate() ↗](https://developer.mozilla.org/en-US/docs/WebAssembly/JavaScript%5Finterface/instantiate). This can be used to accelerate computationally intensive operations which do not involve significant I/O. This guide demonstrates the basics of Wasm and JavaScript interoperability. ## Simple Wasm Module In this guide, you will use the WebAssembly Text Format to create a simple Wasm module to understand how imports and exports work. In practice, you would not write code in this format. You would instead use the programming language of your choice and compile directly to WebAssembly Binary Format (`.wasm`). Review the following example module (`;;` denotes a comment): ``` ;; src/simple.wat (module ;; Import a function from JavaScript named `imported_func` ;; which takes a single i32 argument and assign to ;; variable $i (func $i (import "imports" "imported_func") (param i32)) ;; Export a function named `exported_func` which takes a ;; single i32 argument and returns an i32 (func (export "exported_func") (param $input i32) (result i32) ;; Invoke `imported_func` with $input as argument local.get $input call $i ;; Return $input local.get $input return ) ) ``` Explain Code Using [wat2wasm ↗](https://github.com/WebAssembly/wabt), convert the WAT format to WebAssembly Binary Format: Terminal window ``` wat2wasm src/simple.wat -o src/simple.wasm ``` ## Bundling Wrangler will bundle any Wasm module that ends in `.wasm` or `.wasm?module`, so that it is available at runtime within your Worker. This is done using a default bundling rule which can be customized in the [Wrangler configuration file](https://developers.cloudflare.com/workers/wrangler/configuration/). Refer to [Wrangler Bundling](https://developers.cloudflare.com/workers/wrangler/bundling/) for more information. ## Use from JavaScript After you have converted the WAT format to WebAssembly Binary Format, import and use the Wasm module in your existing JavaScript or TypeScript Worker: TypeScript ``` import mod from "./simple.wasm"; // Define imports available to Wasm instance. const importObject = { imports: { imported_func: (arg: number) => { console.log(`Hello from JavaScript: ${arg}`); }, }, }; // Create instance of WebAssembly Module `mod`, supplying // the expected imports in `importObject`. This should be // done at the top level of the script to avoid instantiation on every request. const instance = await WebAssembly.instantiate(mod, importObject); export default { async fetch() { // Invoke the `exported_func` from our Wasm Instance with // an argument. const retval = instance.exports.exported_func(42); // Return the return value! return new Response(`Success: ${retval}`); }, }; ``` Explain Code When invoked, this Worker should log `Hello from JavaScript: 42` and return `Success: 42`, demonstrating the ability to invoke Wasm methods with arguments from JavaScript and vice versa. ## Next steps In practice, you will likely compile a language of your choice (such as Rust) to WebAssembly binaries. Many languages provide a `bindgen` to simplify the interaction between JavaScript and Wasm. These tools may integrate with your JavaScript bundler, and provide an API other than the WebAssembly API for initializing and invoking your Wasm module. As an example, refer to the [Rust wasm-bindgen documentation ↗](https://rustwasm.github.io/wasm-bindgen/examples/without-a-bundler.html). Alternatively, to write your entire Worker in Rust, Workers provides many of the same [Runtime APIs](https://developers.cloudflare.com/workers/runtime-apis) and [bindings](https://developers.cloudflare.com/workers/runtime-apis/bindings/) when using the `workers-rs` crate. For more information, refer to the [Workers Rust guide](https://developers.cloudflare.com/workers/languages/rust/). ```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/runtime-apis/","name":"Runtime APIs"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/runtime-apis/webassembly/","name":"WebAssembly (Wasm)"}},{"@type":"ListItem","position":5,"item":{"@id":"/workers/runtime-apis/webassembly/javascript/","name":"Wasm in JavaScript"}}]} ``` --- --- title: WebSockets description: Communicate in real time with your Cloudflare Workers. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/runtime-apis/websockets.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # WebSockets ## Background WebSockets allow you to communicate in real time with your Cloudflare Workers serverless functions. For a complete example, refer to [Using the WebSockets API](https://developers.cloudflare.com/workers/examples/websockets/). Note If your application needs to coordinate among multiple WebSocket connections, such as a chat room or game match, you will need clients to send messages to a single-point-of-coordination. Durable Objects provide a single-point-of-coordination for Cloudflare Workers, and are often used in parallel with WebSockets to persist state over multiple clients and connections. In this case, refer to [Durable Objects](https://developers.cloudflare.com/durable-objects/) to get started, and prefer using the Durable Objects' extended [WebSockets API](https://developers.cloudflare.com/durable-objects/best-practices/websockets/). ## Constructor JavaScript ``` // { 0: , 1: } let websocketPair = new WebSocketPair(); ``` The WebSocketPair returned from this constructor is an Object, with two WebSockets at keys `0` and `1`. These WebSockets are commonly referred to as `client` and `server`. The below example combines `Object.values` and ES6 destructuring to retrieve the WebSockets as `client` and `server`: JavaScript ``` let [client, server] = Object.values(new WebSocketPair()); ``` ## Methods ### accept * `accept(options?)` * Accepts the WebSocket connection and begins terminating requests for the WebSocket on Cloudflare's global network. This effectively enables the Workers runtime to begin responding to and handling WebSocket requests. #### Parameters * `options` object optional * An optional configuration object with the following properties: * `allowHalfOpen` boolean optional — When `true`, the runtime will not automatically send a reciprocal Close frame when a Close frame is received from the peer. Instead, `readyState` remains `CLOSING` until you explicitly call `close()`. This is useful for [WebSocket proxying](#close-behavior) where you need to coordinate the close across both sides of the proxy. Defaults to `false`. ### addEventListener * `addEventListener(eventWebSocketEvent, callbackFunctionFunction)` * Add callback functions to be executed when an event has occurred on the WebSocket. #### Parameters * `event` WebSocketEvent * The WebSocket event (refer to [Events](https://developers.cloudflare.com/workers/runtime-apis/websockets/#events)) to listen to. * `callbackFunction(messageMessage)` Function * A function to be called when the WebSocket responds to a specific event. ### close * `close(codenumber, reasonstring)` * Close the WebSocket connection. #### Parameters * `codeinteger` optional * An integer indicating the close code sent by the server. This should match an option from the [list of status codes ↗](https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent#status%5Fcodes) provided by the WebSocket spec. * `reasonstring` optional * A human-readable string indicating why the WebSocket connection was closed. ### send * `send(messagestring | ArrayBuffer | ArrayBufferView)` * Send a message to the other WebSocket in this WebSocket pair. #### Parameters * `messagestring` * The message to send down the WebSocket connection to the corresponding client. This should be a string or something coercible into a string; for example, strings and numbers will be simply cast into strings, but objects and arrays should be cast to JSON strings using `JSON.stringify`, and parsed in the client. --- ## Properties ### readyState * `readyState` number * Returns the current state of the WebSocket connection. Possible values: | Constant | Value | Description | | -------------------- | ----- | ------------------------------------------------ | | WebSocket.CONNECTING | 0 | The connection is not yet open. | | WebSocket.OPEN | 1 | The connection is open and ready to communicate. | | WebSocket.CLOSING | 2 | The connection is in the process of closing. | | WebSocket.CLOSED | 3 | The connection is closed. | --- ## Events * `close` * An event indicating the WebSocket has closed. The `CloseEvent` includes `code` (number), `reason` (string), and `wasClean` (boolean) properties. * `error` * An event indicating there was an error with the WebSocket. * `message` * An event indicating a new message received from the client, including the data passed by the client. Note WebSocket messages received by a Worker have a size limit of 32 MiB (33,554,432 bytes). If a larger message is sent, the WebSocket will be automatically closed with a `1009` "Message is too large" response. ## Types ### Message * `data` any - The data passed back from the other WebSocket in your pair. * `type` string - Defaults to `message`. --- ## Close behavior With the [web\_socket\_auto\_reply\_to\_close](https://developers.cloudflare.com/workers/configuration/compatibility-flags/#websocket-auto-reply-to-close) compatibility flag (enabled by default on compatibility dates on or after `2026-04-07`), the Workers runtime automatically sends a reciprocal Close frame when it receives a Close frame from the peer. The `readyState` transitions to `CLOSED` before the `close` event fires. This matches the [WebSocket specification ↗](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/close%5Fevent) and standard browser behavior. If you still call `close()` inside the `close` event handler, the call is silently ignored. Existing code that manually replies to Close frames will continue to work without changes. JavaScript ``` server.addEventListener("close", (event) => { // readyState is already CLOSED — no need to call server.close(). console.log(server.readyState); // WebSocket.CLOSED console.log(event.code); // 1000 console.log(event.wasClean); // true }); ``` ### Half-open mode for proxying The automatic close behavior can interfere with WebSocket proxying, where a Worker sits between a client and a backend and needs to coordinate the close on both sides independently. To support this, pass `{ allowHalfOpen: true }` to `accept()`: JavaScript ``` server.accept({ allowHalfOpen: true }); server.addEventListener("close", (event) => { // readyState is still CLOSING here, giving you time // to coordinate the close on the other side. console.log(server.readyState); // WebSocket.CLOSING // Manually close when ready. server.close(event.code, "done"); }); ``` Explain Code Note WebSockets created with `new WebSocket(url)` always auto-reply to Close frames after this flag takes effect. There is no way to pass `allowHalfOpen` because these WebSockets are automatically accepted. If you need half-open behavior for a client WebSocket, use `fetch()` with the `Upgrade: websocket` header instead, then call `resp.webSocket.accept({ allowHalfOpen: true })`. ### Prior behavior On compatibility dates before `2026-04-07` (or with the `web_socket_manual_reply_to_close` flag), receiving a Close frame leaves the WebSocket in `CLOSING` state, and your code must call `close()` to complete the handshake. Failing to do so can result in `1006` abnormal closure errors on the client. --- ## Related resources * [Mozilla Developer Network's (MDN) documentation on the WebSocket class ↗](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket) * [Our WebSocket template for building applications on Workers using WebSockets ↗](https://github.com/cloudflare/websocket-template) ```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/runtime-apis/","name":"Runtime APIs"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/runtime-apis/websockets/","name":"WebSockets"}}]} ``` --- --- title: Static Assets description: Create full-stack applications deployed to Cloudflare Workers. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/static-assets/index.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Static Assets You can upload static assets (HTML, CSS, images and other files) as part of your Worker, and Cloudflare will handle caching and serving them to web browsers. **Start from CLI** \- Scaffold a React SPA with an API Worker, and use the [Cloudflare Vite plugin](https://developers.cloudflare.com/workers/vite-plugin/). npm yarn pnpm ``` npm create cloudflare@latest -- my-react-app --framework=react ``` ``` yarn create cloudflare my-react-app --framework=react ``` ``` pnpm create cloudflare@latest my-react-app --framework=react ``` --- **Or just deploy to Cloudflare** [![Deploy to Workers](https://deploy.workers.cloudflare.com/button)](https://dash.cloudflare.com/?to=/:account/workers-and-pages/create/deploy-to-workers&repository=https://github.com/cloudflare/templates/tree/main/vite-react-template) Learn more about supported frameworks on Workers. [ Supported frameworks ](https://developers.cloudflare.com/workers/framework-guides/) Start building on Workers with our framework guides. ### How it works When you deploy your project, Cloudflare deploys both your Worker code and your static assets in a single operation. This deployment operates as a tightly integrated "unit" running across Cloudflare's network, combining static file hosting, custom logic, and global caching. The **assets directory** specified in your [Wrangler configuration file](https://developers.cloudflare.com/workers/wrangler/configuration/#assets) is central to this design. During deployment, Wrangler automatically uploads the files from this directory to Cloudflare's infrastructure. Once deployed, requests for these assets are routed efficiently to locations closest to your users. * [ wrangler.jsonc ](#tab-panel-7834) * [ wrangler.toml ](#tab-panel-7835) JSONC ``` { "$schema": "./node_modules/wrangler/config-schema.json", "name": "my-spa", "main": "src/index.js", // Set this to today's date "compatibility_date": "2026-04-14", "assets": { "directory": "./dist", "binding": "ASSETS" } } ``` Explain Code TOML ``` "$schema" = "./node_modules/wrangler/config-schema.json" name = "my-spa" main = "src/index.js" # Set this to today's date compatibility_date = "2026-04-14" [assets] directory = "./dist" binding = "ASSETS" ``` Note If you are using the [Cloudflare Vite plugin](https://developers.cloudflare.com/workers/vite-plugin/), you do not need to specify `assets.directory`. For more information about using static assets with the Vite plugin, refer to the [plugin documentation](https://developers.cloudflare.com/workers/vite-plugin/reference/static-assets/). By adding an [**assets binding**](https://developers.cloudflare.com/workers/static-assets/binding/#binding), you can directly fetch and serve assets within your Worker code. * [ JavaScript ](#tab-panel-7830) * [ Python ](#tab-panel-7831) JavaScript ``` // index.js export default { async fetch(request, env) { const url = new URL(request.url); if (url.pathname.startsWith("/api/")) { return new Response(JSON.stringify({ name: "Cloudflare" }), { headers: { "Content-Type": "application/json" }, }); } return env.ASSETS.fetch(request); }, }; ``` Explain Code Python ``` from workers import WorkerEntrypoint, Response from urllib.parse import urlparse class Default(WorkerEntrypoint): async def fetch(self, request): # Example of serving static assets url = urlparse(request.url) if url.path.startswith("/api/): return Response.json({"name": "Cloudflare"}) return await self.env.ASSETS.fetch(request) ``` Explain Code ### Routing behavior By default, if a requested URL matches a file in the static assets directory, that file will be served — without invoking Worker code. If no matching asset is found and a Worker script is present, the request will be processed by the Worker. The Worker can return a response or choose to defer again to static assets by using the [assets binding](https://developers.cloudflare.com/workers/static-assets/binding/) (e.g. `env.ASSETS.fetch(request)`). If no Worker script is present, a `404 Not Found` response is returned. The default behavior for requests which don't match a static asset can be changed by setting the [not\_found\_handling option under assets](https://developers.cloudflare.com/workers/wrangler/configuration/#assets) in your Wrangler configuration file: * [not\_found\_handling = "single-page-application"](https://developers.cloudflare.com/workers/static-assets/routing/single-page-application/): Sets your application to return a `200 OK` response with `index.html` for requests which don't match a static asset. Use this if you have a Single Page Application. We recommend pairing this with selective routing using `run_worker_first` for [advanced routing control](https://developers.cloudflare.com/workers/static-assets/routing/single-page-application/#advanced-routing-control). * [not\_found\_handling = "404-page"](https://developers.cloudflare.com/workers/static-assets/routing/static-site-generation/#custom-404-pages): Sets your application to return a `404 Not Found` response with the nearest `404.html` for requests which don't match a static asset. * [ wrangler.jsonc ](#tab-panel-7832) * [ wrangler.toml ](#tab-panel-7833) JSONC ``` { "assets": { "directory": "./dist", "not_found_handling": "single-page-application" } } ``` TOML ``` [assets] directory = "./dist" not_found_handling = "single-page-application" ``` If you want the Worker code to execute before serving assets, you can use the `run_worker_first` option. This can be set to `true` to invoke the Worker script for all requests, or configured as an array of route patterns for selective Worker-script-first routing: **Invoking your Worker script on specific paths:** * [ wrangler.jsonc ](#tab-panel-7836) * [ wrangler.toml ](#tab-panel-7837) JSONC ``` { "name": "my-spa-worker", // Set this to today's date "compatibility_date": "2026-04-14", "main": "./src/index.ts", "assets": { "directory": "./dist/", "not_found_handling": "single-page-application", "binding": "ASSETS", "run_worker_first": ["/api/*", "!/api/docs/*"] } } ``` Explain Code TOML ``` name = "my-spa-worker" # Set this to today's date compatibility_date = "2026-04-14" main = "./src/index.ts" [assets] directory = "./dist/" not_found_handling = "single-page-application" binding = "ASSETS" run_worker_first = [ "/api/*", "!/api/docs/*" ] ``` Explain Code For a more advanced pattern, refer to [SPA shell with bootstrap data](https://developers.cloudflare.com/workers/examples/spa-shell/), which uses HTMLRewriter to inject prefetched API data into the HTML stream. [ Routing options ](https://developers.cloudflare.com/workers/static-assets/routing/) Learn more about how you can customize routing behavior. ### Caching behavior Cloudflare provides automatic caching for static assets across its network, ensuring fast delivery to users worldwide. When a static asset is requested, it is automatically cached for future requests. * **First Request:** When an asset is requested for the first time, it is fetched from storage and cached at the nearest Cloudflare location. * **Subsequent Requests:** If a request for the same asset reaches a data center that does not have it cached, Cloudflare's [tiered caching system](https://developers.cloudflare.com/cache/how-to/tiered-cache/) allows it to be retrieved from a nearby cache rather than going back to storage. This improves cache hit ratio, reduces latency, and reduces unnecessary origin fetches. ## Try it out [ Vite + React SPA tutorial ](https://developers.cloudflare.com/workers/vite-plugin/tutorial/) Learn how to build and deploy a full-stack Single Page Application with static assets and API routes. ## Learn more [ Supported frameworks ](https://developers.cloudflare.com/workers/framework-guides/) Start building on Workers with our framework guides. [ Billing and limitations ](https://developers.cloudflare.com/workers/static-assets/billing-and-limitations/) Learn more about how requests are billed, current limitations, and troubleshooting. ```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/static-assets/","name":"Static Assets"}}]} ``` --- --- title: Billing and Limitations description: Billing, troubleshooting, and limitations for Static assets on Workers image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/static-assets/billing-and-limitations.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Billing and Limitations ## Billing Requests to a project with static assets can either return static assets or invoke the Worker script, depending on if the request [matches a static asset or not](https://developers.cloudflare.com/workers/static-assets/routing/). * Requests to static assets are free and unlimited. Requests to the Worker script (for example, in the case of SSR content) are billed according to Workers pricing. Refer to [pricing](https://developers.cloudflare.com/workers/platform/pricing/#example-2) for an example. * There is no additional cost for storing Assets. * **Important note for free tier users**: When using [run\_worker\_first](https://developers.cloudflare.com/workers/static-assets/binding/#run%5Fworker%5Ffirst), requests matching the specified patterns will always invoke your Worker script. If you exceed your free tier request limits, these requests will receive a 429 (Too Many Requests) response instead of falling back to static asset serving. Negative patterns (patterns beginning with `!/`) will continue to serve assets correctly, as requests are directed to assets, without invoking your Worker script. ## Limitations See the [Platform Limits](https://developers.cloudflare.com/workers/platform/limits/#static-assets) ## Troubleshooting * `assets.bucket is a required field` — if you see this error, you need to update Wrangler to at least `3.78.10` or later. `bucket` is not a required field. ```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/static-assets/","name":"Static Assets"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/static-assets/billing-and-limitations/","name":"Billing and Limitations"}}]} ``` --- --- title: Configuration and Bindings description: Details on how to configure Workers static assets and its binding. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) ### Tags [ Bindings ](https://developers.cloudflare.com/search/?tags=Bindings) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/static-assets/binding.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Configuration and Bindings Configuring a Worker with assets requires specifying a [directory](https://developers.cloudflare.com/workers/static-assets/binding/#directory) and, optionally, an [assets binding](https://developers.cloudflare.com/workers/static-assets/binding/), in your Worker's Wrangler file. The [assets binding](https://developers.cloudflare.com/workers/static-assets/binding/) allows you to dynamically fetch assets from within your Worker script (e.g. `env.ASSETS.fetch()`), similarly to how you might with a make a `fetch()` call with a [Service binding](https://developers.cloudflare.com/workers/runtime-apis/bindings/service-bindings/http/). Only one collection of static assets can be configured in each Worker. ## `directory` The folder of static assets to be served. For many frameworks, this is the `./public/`, `./dist/`, or `./build/` folder. * [ wrangler.jsonc ](#tab-panel-7840) * [ wrangler.toml ](#tab-panel-7841) JSONC ``` { "$schema": "./node_modules/wrangler/config-schema.json", "name": "my-worker", // Set this to today's date "compatibility_date": "2026-04-14", "assets": { "directory": "./public/", }, } ``` TOML ``` "$schema" = "./node_modules/wrangler/config-schema.json" name = "my-worker" # Set this to today's date compatibility_date = "2026-04-14" [assets] directory = "./public/" ``` ### Ignoring assets Sometime there are files in the asset directory that should not be uploaded. In this case, create a `.assetsignore` file in the root of the assets directory. This file takes the same format as `.gitignore`. Wrangler will not upload asset files that match lines in this file. **Example** You are migrating from a Pages project where the assets directory is `dist`. You do not want to upload the server-side Worker code nor Pages configuration files as public client-side assets. Add the following `.assetsignore` file: ``` _worker.js _redirects _headers ``` Now Wrangler will not upload these files as client-side assets when deploying the Worker. ## `run_worker_first` Controls whether to invoke the Worker script regardless of a request which would have otherwise matched an asset. `run_worker_first = false` (default) will serve any static asset matching a request, while `run_worker_first = true` will unconditionally [invoke your Worker script](https://developers.cloudflare.com/workers/static-assets/routing/worker-script/#run-your-worker-script-first). * [ wrangler.jsonc ](#tab-panel-7842) * [ wrangler.toml ](#tab-panel-7843) JSONC ``` { "$schema": "./node_modules/wrangler/config-schema.json", "name": "my-worker", // Set this to today's date "compatibility_date": "2026-04-14", "main": "src/index.ts", // The following configuration unconditionally invokes the Worker script at // `src/index.ts`, which can programatically fetch assets via the ASSETS binding "assets": { "directory": "./public/", "binding": "ASSETS", "run_worker_first": true, }, } ``` Explain Code TOML ``` "$schema" = "./node_modules/wrangler/config-schema.json" name = "my-worker" # Set this to today's date compatibility_date = "2026-04-14" main = "src/index.ts" [assets] directory = "./public/" binding = "ASSETS" run_worker_first = true ``` Explain Code You can also specify `run_worker_first` as an array of route patterns to selectively run the Worker script first only for specific routes. The array supports glob patterns with `*` for deep matching and negative patterns with `!` prefix. Negative patterns have precedence over non-negative patterns. The Worker will run first when a non-negative pattern matches and none of the negative pattern matches. The order in which the patterns are listed is not significant. `run_worker_first` is often paired with the [not\_found\_handling = "single-page-application" setting](https://developers.cloudflare.com/workers/static-assets/routing/single-page-application/#advanced-routing-control): * [ wrangler.jsonc ](#tab-panel-7844) * [ wrangler.toml ](#tab-panel-7845) JSONC ``` { "name": "my-spa-worker", // Set this to today's date "compatibility_date": "2026-04-14", "main": "./src/index.ts", "assets": { "directory": "./dist/", "not_found_handling": "single-page-application", "binding": "ASSETS", "run_worker_first": ["/api/*", "!/api/docs/*"] } } ``` Explain Code TOML ``` name = "my-spa-worker" # Set this to today's date compatibility_date = "2026-04-14" main = "./src/index.ts" [assets] directory = "./dist/" not_found_handling = "single-page-application" binding = "ASSETS" run_worker_first = [ "/api/*", "!/api/docs/*" ] ``` Explain Code In this configuration, requests to `/api/*` routes will invoke the Worker script first, except for `/api/docs/*` which will follow the default asset-first routing behavior. Common uses for `run_worker_first` include authentication checks, A/B testing, and [injecting bootstrap data into your SPA shell](https://developers.cloudflare.com/workers/examples/spa-shell/). ## `binding` Configuring the optional [binding](https://developers.cloudflare.com/workers/runtime-apis/bindings) gives you access to the collection of assets from within your Worker script. * [ wrangler.jsonc ](#tab-panel-7846) * [ wrangler.toml ](#tab-panel-7847) JSONC ``` { "$schema": "./node_modules/wrangler/config-schema.json", "name": "my-worker", "main": "./src/index.js", // Set this to today's date "compatibility_date": "2026-04-14", "assets": { "directory": "./public/", "binding": "ASSETS", }, } ``` Explain Code TOML ``` "$schema" = "./node_modules/wrangler/config-schema.json" name = "my-worker" main = "./src/index.js" # Set this to today's date compatibility_date = "2026-04-14" [assets] directory = "./public/" binding = "ASSETS" ``` In the example above, assets would be available through `env.ASSETS`. ### Runtime API Reference #### `fetch()` **Parameters** * `request: Request | URL | string` Pass a [Request object](https://developers.cloudflare.com/workers/runtime-apis/request/), URL object, or URL string. Requests made through this method have `html_handling` and `not_found_handling` configuration applied to them. **Response** * `Promise` Returns a static asset response for the given request. **Example** Your dynamic code can make new, or forward incoming requests to your project's static assets using the assets binding. For example, `env.ASSETS.fetch(request)`, `env.ASSETS.fetch(new URL('https://assets.local/my-file'))` or `env.ASSETS.fetch('https://assets.local/my-file')`. The hostname used in the URL (for example, `assets.local`) is not meaningful — any valid hostname will work. Only the URL pathname is used to match assets. Note If you need to fetch assets from within an [RPC method](https://developers.cloudflare.com/workers/runtime-apis/rpc/#fetching-static-assets) (where there is no incoming `request`), construct a URL using any hostname — for example, `this.env.ASSETS.fetch(new Request('https://assets.local/path/to/asset'))`. Take the following example that configures a Worker script to return a response under all requests headed for `/api/`. Otherwise, the Worker script will pass the incoming request through to the asset binding. In this case, because a Worker script is only invoked when the requested route has not matched any static assets, this will always evaluate [not\_found\_handling](https://developers.cloudflare.com/workers/static-assets/#routing-behavior) behavior. * [ JavaScript ](#tab-panel-7838) * [ TypeScript ](#tab-panel-7839) JavaScript ``` export default { async fetch(request, env) { const url = new URL(request.url); if (url.pathname.startsWith("/api/")) { // TODO: Add your custom /api/* logic here. return new Response("Ok"); } // Passes the incoming request through to the assets binding. // No asset matched this request, so this will evaluate `not_found_handling` behavior. return env.ASSETS.fetch(request); }, }; ``` Explain Code TypeScript ``` interface Env { ASSETS: Fetcher; } export default { async fetch(request, env): Promise { const url = new URL(request.url); if (url.pathname.startsWith("/api/")) { // TODO: Add your custom /api/* logic here. return new Response("Ok"); } // Passes the incoming request through to the assets binding. // No asset matched this request, so this will evaluate `not_found_handling` behavior. return env.ASSETS.fetch(request); }, } satisfies ExportedHandler; ``` Explain Code ## Routing configuration For the various static asset routing configuration options, refer to [Routing](https://developers.cloudflare.com/workers/static-assets/routing/). ## Smart Placement [Smart Placement](https://developers.cloudflare.com/workers/configuration/placement/) can be used to place a Worker's code close to your back-end infrastructure. Smart Placement will only have an effect if you specified a `main`, pointing to your Worker code. ### Smart Placement with Worker Code First If you desire to run your [Worker code ahead of assets](https://developers.cloudflare.com/workers/static-assets/routing/worker-script/#run-your-worker-script-first) by setting `run_worker_first=true`, all requests must first travel to your Smart-Placed Worker. As a result, you may experience increased latency for asset requests. Use Smart Placement with `run_worker_first=true` when you need to integrate with other backend services, authenticate requests before serving any assets, or if you want to make modifications to your assets before serving them. If you want some assets served as quickly as possible to the user, but others to be served behind a smart-placed Worker, considering splitting your app into multiple Workers and [using service bindings to connect them](https://developers.cloudflare.com/workers/configuration/placement/#multiple-workers). ### Smart Placement with Assets First Enabling Smart Placement with `run_worker_first=false` (or not specifying it) lets you serve assets from as close as possible to your users, but moves your Worker logic to run most efficiently (such as near a database). Use Smart Placement with `run_worker_first=false` (or not specifying it) when prioritizing fast asset delivery. This will not impact the [default routing behavior](https://developers.cloudflare.com/workers/static-assets/#routing-behavior). ```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/static-assets/","name":"Static Assets"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/static-assets/binding/","name":"Configuration and Bindings"}}]} ``` --- --- title: Direct Uploads description: Upload assets through the Workers API. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/static-assets/direct-upload.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Direct Uploads Note Directly uploading assets via APIs is an advanced approach which, unless you are building a programatic integration, most users will not need. Instead, we encourage users to deploy your Worker with [Wrangler](https://developers.cloudflare.com/workers/static-assets/get-started/#1-create-a-new-worker-project-using-the-cli). Our API empowers users to upload and include static assets as part of a Worker. These static assets can be served for free, and additionally, users can also fetch assets through an optional [assets binding](https://developers.cloudflare.com/workers/static-assets/binding/) to power more advanced applications. This guide will describe the process for attaching assets to your Worker directly with the API. * [ Workers ](#tab-panel-7848) * [ Workers for Platforms ](#tab-panel-7849) sequenceDiagram participant User participant Workers API User<<->>Workers API: Submit manifest
POST /client/v4/accounts/:accountId/workers/scripts/:scriptName/assets-upload-session User<<->>Workers API: Upload files
POST /client/v4/accounts/:accountId/workers/assets/upload?base64=true User<<->>Workers API: Upload script version
PUT /client/v4/accounts/:accountId/workers/scripts/:scriptName sequenceDiagram participant User participant Workers API User<<->>Workers API: Submit manifest
POST /client/v4/accounts/:accountId/workers/dispatch/namespaces/:dispatchNamespace/scripts/:scriptName/assets-upload-session User<<->>Workers API: Upload files
POST /client/v4/accounts/:accountId/workers/assets/upload?base64=true User<<->>Workers API: Upload script version
PUT /client/v4/accounts/:accountId/workers/dispatch/namespaces/:dispatchNamespace/scripts/:scriptName The asset upload flow can be distilled into three distinct phases: 1. Registration of a manifest 2. Upload of the assets 3. Deployment of the Worker ## Upload manifest The asset manifest is a ledger which keeps track of files we want to use in our Worker. This manifest is used to track assets associated with each Worker version, and eliminate the need to upload unchanged files prior to a new upload. The [manifest upload request](https://developers.cloudflare.com/api/resources/workers/subresources/scripts/subresources/assets/subresources/upload/methods/create/) describes each file which we intend to upload. Each file is its own key representing the file path and name, and is an object which contains metadata about the file. `hash` represents a 32 hexadecimal character hash of the file, while `size` is the size (in bytes) of the file. * [ Workers ](#tab-panel-7850) * [ Workers for Platforms ](#tab-panel-7851) Terminal window ``` curl -X POST https://api.cloudflare.com/client/v4/accounts/{account_id}/workers/scripts/{script_name}/assets-upload-session \ --header 'content-type: application/json' \ --header 'Authorization: Bearer ' \ --data '{ "manifest": { "/filea.html": { "hash": "08f1dfda4574284ab3c21666d1", "size": 12 }, "/fileb.html": { "hash": "4f1c1af44620d531446ceef93f", "size": 23 }, "/filec.html": { "hash": "54995e302614e0523757a04ec1", "size": 23 } } }' ``` Explain Code Terminal window ``` curl -X POST https://api.cloudflare.com/client/v4/accounts/{account_id}/workers/dispatch/namespaces/{dispatch_namespace}/scripts/{script_name}/assets-upload-session \ --header 'content-type: application/json' \ --header 'Authorization: Bearer ' \ --data '{ "manifest": { "/filea.html": { "hash": "08f1dfda4574284ab3c21666d1", "size": 12 }, "/fileb.html": { "hash": "4f1c1af44620d531446ceef93f", "size": 23 }, "/filec.html": { "hash": "54995e302614e0523757a04ec1", "size": 23 } } }' ``` Explain Code The resulting response will contain a JWT, which provides authentication during file upload. The JWT is valid for one hour. In addition to the JWT, the response instructs users how to optimally batch upload their files. These instructions are encoded in the `buckets` field. Each array in `buckets` contains a list of file hashes which should be uploaded together. Unmodified files will not be returned in the `buckets` field (as they do not need to be re-uploaded) if they have recently been uploaded in previous versions of your Worker. ``` { "result": { "jwt": "", "buckets": [ ["08f1dfda4574284ab3c21666d1", "4f1c1af44620d531446ceef93f"], ["54995e302614e0523757a04ec1"] ] }, "success": true, "errors": null, "messages": null } ``` Explain Code Note If all assets have been previously uploaded, `buckets` will be empty, and `jwt` will contain a completion token. Uploading files is not necessary, and you can skip directly to [uploading a new script or version](https://developers.cloudflare.com/workers/static-assets/direct-upload/#createdeploy-new-version). ### Limitations * Limits differ based on account plan. Refer to [Account Plan Limits](https://developers.cloudflare.com/workers/platform/limits/#account-plan-limits) for more information on limitations of static assets. ## Upload Static Assets The [file upload API](https://developers.cloudflare.com/api/resources/workers/subresources/assets/subresources/upload/methods/create/) requires files be uploaded using `multipart/form-data`. The contents of each file must be base64 encoded, and the `base64` query parameter in the URL must be set to `true`. The provided `Content-Type` header of each file part will be attached when eventually serving the file. If you wish to avoid sending a `Content-Type` header in your deployment, `application/null` may be sent at upload time. The `Authorization` header must be provided as a bearer token, using the JWT (upload token) from the aforementioned manifest upload call. Once every file in the manifest has been uploaded, a status code of 201 will be returned, with the `jwt` field present. This JWT is a final "completion" token which can be used to create a deployment of a Worker with this set of assets. This completion token is valid for 1 hour. ## Create/Deploy New Version [Script](https://developers.cloudflare.com/api/resources/workers/subresources/scripts/methods/update/), [Version](https://developers.cloudflare.com/api/resources/workers/subresources/scripts/subresources/versions/methods/create/), and [Workers for Platform script](https://developers.cloudflare.com/api/resources/workers%5Ffor%5Fplatforms/subresources/dispatch/subresources/namespaces/subresources/scripts/methods/update/) upload endpoints require specifying a metadata part in the form data. Here, we can provide the completion token from the previous (upload assets) step. Example Worker Metadata Specifying Completion Token ``` { "main_module": "main.js", "assets": { "jwt": "" }, "compatibility_date": "2021-09-14" } ``` If this is a Worker which already has assets, and you wish to just re-use the existing set of assets, we do not have to specify the completion token again. Instead, we can pass the boolean `keep_assets` option. Example Worker Metadata Specifying keep\_assets ``` { "main_module": "main.js", "keep_assets": true, "compatibility_date": "2021-09-14" } ``` Asset [routing configuration](https://developers.cloudflare.com/workers/wrangler/configuration/#assets) can be provided in the `assets` object, such as `html_handling` and `not_found_handling`. Example Worker Metadata Specifying Asset Configuration ``` { "main_module": "main.js", "assets": { "jwt": "", "config" { "html_handling": "auto-trailing-slash" } }, "compatibility_date": "2021-09-14" } ``` Explain Code Optionally, an assets binding can be provided if you wish to fetch and serve assets from within your Worker code. Example Worker Metadata Specifying Asset Binding ``` { "main_module": "main.js", "assets": { ... }, "bindings": [ ... { "name": "ASSETS", "type": "assets" } ... ] "compatibility_date": "2021-09-14" } ``` Explain Code ## Programmatic Example This example is from [cloudflare-typescript ↗](https://github.com/cloudflare/cloudflare-typescript/blob/main/examples/workers/script-with-assets-upload.ts). * [ JavaScript ](#tab-panel-7852) * [ TypeScript ](#tab-panel-7853) JavaScript ``` #!/usr/bin/env -S npm run tsn -T /** * Create a Worker that serves static assets * * This example demonstrates how to: * - Upload static assets to Cloudflare Workers * - Create and deploy a Worker that serves those assets * * Docs: * - https://developers.cloudflare.com/workers/static-assets/direct-upload * * Prerequisites: * 1. Generate an API token: https://developers.cloudflare.com/fundamentals/api/get-started/create-token/ * 2. Find your account ID: https://developers.cloudflare.com/fundamentals/setup/find-account-and-zone-ids/ * 3. Find your workers.dev subdomain: https://developers.cloudflare.com/workers/configuration/routing/workers-dev/ * * Environment variables: * - CLOUDFLARE_API_TOKEN (required) * - CLOUDFLARE_ACCOUNT_ID (required) * - ASSETS_DIRECTORY (required) * - CLOUDFLARE_SUBDOMAIN (optional) * * Usage: * Place your static files in the ASSETS_DIRECTORY, then run this script. * Assets will be available at: my-script-with-assets.$subdomain.workers.dev/$filename */ import crypto from "crypto"; import fs from "fs"; import { readFile } from "node:fs/promises"; import { extname } from "node:path"; import path from "path"; import { exit } from "node:process"; import Cloudflare from "cloudflare"; const WORKER_NAME = "my-worker-with-assets"; const SCRIPT_FILENAME = `${WORKER_NAME}.mjs`; function loadConfig() { const apiToken = process.env["CLOUDFLARE_API_TOKEN"]; if (!apiToken) { throw new Error( "Missing required environment variable: CLOUDFLARE_API_TOKEN", ); } const accountId = process.env["CLOUDFLARE_ACCOUNT_ID"]; if (!accountId) { throw new Error( "Missing required environment variable: CLOUDFLARE_ACCOUNT_ID", ); } const assetsDirectory = process.env["ASSETS_DIRECTORY"]; if (!assetsDirectory) { throw new Error("Missing required environment variable: ASSETS_DIRECTORY"); } if (!fs.existsSync(assetsDirectory)) { throw new Error(`Assets directory does not exist: ${assetsDirectory}`); } const subdomain = process.env["CLOUDFLARE_SUBDOMAIN"]; return { apiToken, accountId, assetsDirectory, subdomain: subdomain || undefined, workerName: WORKER_NAME, }; } const config = loadConfig(); const client = new Cloudflare({ apiToken: config.apiToken, }); /** * Recursively reads all files from a directory and creates a manifest * mapping file paths to their hash and size. */ function createManifest(directory) { const manifest = {}; function processDirectory(currentDir, basePath = "") { try { const entries = fs.readdirSync(currentDir, { withFileTypes: true }); for (const entry of entries) { const fullPath = path.join(currentDir, entry.name); const relativePath = path.join(basePath, entry.name); if (entry.isDirectory()) { processDirectory(fullPath, relativePath); } else if (entry.isFile()) { try { const fileContent = fs.readFileSync(fullPath); const extension = extname(relativePath).substring(1); // Generate a hash for the file const hash = crypto .createHash("sha256") .update(fileContent.toString("base64") + extension) .digest("hex") .slice(0, 32); // Normalize path separators to forward slashes const manifestPath = `/${relativePath.replace(/\\/g, "/")}`; manifest[manifestPath] = { hash, size: fileContent.length, }; console.log( `Added to manifest: ${manifestPath} (${fileContent.length} bytes)`, ); } catch (error) { console.warn(`Failed to process file ${fullPath}:`, error); } } } } catch (error) { throw new Error(`Failed to read directory ${currentDir}: ${error}`); } } processDirectory(directory); if (Object.keys(manifest).length === 0) { throw new Error(`No files found in assets directory: ${directory}`); } console.log(`Created manifest with ${Object.keys(manifest).length} files`); return manifest; } /** * Generates the Worker script content that serves static assets */ function generateWorkerScript(exampleFile) { return ` export default { async fetch(request, env, ctx) { const url = new URL(request.url); // Serve a simple index page at the root if (url.pathname === '/') { return new Response( \` Static Assets Worker

This Worker serves static assets!

To access your assets, add /filename to the URL.

Try visiting \${url.origin}/${exampleFile}">/${exampleFile}

\`, { status: 200, headers: { 'Content-Type': 'text/html' } } ); } // Serve static assets for all other paths return env.ASSETS.fetch(request); } }; `.trim(); } /** * Creates upload payloads from buckets and manifest */ async function createUploadPayloads(buckets, manifest, assetsDirectory) { const payloads = []; for (const bucket of buckets) { const payload = {}; for (const hash of bucket) { // Find the file path for this hash const manifestEntry = Object.entries(manifest).find( ([_, data]) => data.hash === hash, ); if (!manifestEntry) { throw new Error(`Could not find file for hash: ${hash}`); } const [relativePath] = manifestEntry; const fullPath = path.join(assetsDirectory, relativePath); try { const fileContent = await readFile(fullPath); payload[hash] = fileContent.toString("base64"); console.log(`Prepared for upload: ${relativePath}`); } catch (error) { throw new Error(`Failed to read file ${fullPath}: ${error}`); } } payloads.push(payload); } return payloads; } /** * Uploads asset payloads */ async function uploadAssets(payloads, uploadJwt, accountId) { let completionJwt; console.log(`Uploading ${payloads.length} payload(s)...`); for (let i = 0; i < payloads.length; i++) { const payload = payloads[i]; console.log(`Uploading payload ${i + 1}/${payloads.length}...`); try { const response = await client.workers.assets.upload.create( { account_id: accountId, base64: true, body: payload, }, { headers: { Authorization: `Bearer ${uploadJwt}` }, }, ); if (response?.jwt) { completionJwt = response.jwt; } } catch (error) { throw new Error(`Failed to upload payload ${i + 1}: ${error}`); } } if (!completionJwt) { throw new Error("Upload completed but no completion JWT received"); } console.log("✅ All assets uploaded successfully"); return completionJwt; } async function main() { try { console.log( "🚀 Starting Worker creation and deployment with static assets...", ); console.log(`📁 Assets directory: ${config.assetsDirectory}`); console.log("📝 Creating asset manifest..."); const manifest = createManifest(config.assetsDirectory); const exampleFile = Object.keys(manifest)[0]?.replace(/^\//, "") || "file.txt"; const scriptContent = generateWorkerScript(exampleFile); let worker; try { worker = await client.workers.beta.workers.get(config.workerName, { account_id: config.accountId, }); console.log(`♻️ Worker ${config.workerName} already exists. Using it.`); } catch (error) { if (!(error instanceof Cloudflare.NotFoundError)) { throw error; } console.log(`✏️ Creating Worker ${config.workerName}...`); worker = await client.workers.beta.workers.create({ account_id: config.accountId, name: config.workerName, subdomain: { enabled: config.subdomain !== undefined, }, observability: { enabled: true, }, }); } console.log(`⚙️ Worker id: ${worker.id}`); console.log("🔄 Starting asset upload session..."); const uploadResponse = await client.workers.scripts.assets.upload.create( config.workerName, { account_id: config.accountId, manifest, }, ); const { buckets, jwt: uploadJwt } = uploadResponse; if (!uploadJwt || !buckets) { throw new Error("Failed to start asset upload session"); } let completionJwt; if (buckets.length === 0) { console.log("✅ No new assets to upload!"); // Use the initial upload JWT as completion JWT when no uploads are needed completionJwt = uploadJwt; } else { const payloads = await createUploadPayloads( buckets, manifest, config.assetsDirectory, ); completionJwt = await uploadAssets(payloads, uploadJwt, config.accountId); } console.log("✏️ Creating Worker version..."); // Create a new version with assets const version = await client.workers.beta.workers.versions.create( worker.id, { account_id: config.accountId, main_module: SCRIPT_FILENAME, compatibility_date: new Date().toISOString().split("T")[0], bindings: [ { type: "assets", name: "ASSETS", }, ], assets: { jwt: completionJwt, }, modules: [ { name: SCRIPT_FILENAME, content_type: "application/javascript+module", content_base64: Buffer.from(scriptContent).toString("base64"), }, ], }, ); console.log("🚚 Creating Worker deployment..."); // Create a deployment and point all traffic to the version we created await client.workers.scripts.deployments.create(config.workerName, { account_id: config.accountId, strategy: "percentage", versions: [ { percentage: 100, version_id: version.id, }, ], }); console.log("✅ Deployment successful!"); if (config.subdomain) { console.log(` 🌍 Your Worker is live! 📍 Base URL: https://${config.workerName}.${config.subdomain}.workers.dev/ 📄 Try accessing: https://${config.workerName}.${config.subdomain}.workers.dev/${exampleFile} `); } else { console.log(` ⚠️ Set up a route, custom domain, or workers.dev subdomain to access your Worker. Add CLOUDFLARE_SUBDOMAIN to your environment variables to set one up automatically. `); } } catch (error) { console.error("❌ Deployment failed:", error); exit(1); } } main(); ``` Explain Code TypeScript ``` #!/usr/bin/env -S npm run tsn -T /** * Create a Worker that serves static assets * * This example demonstrates how to: * - Upload static assets to Cloudflare Workers * - Create and deploy a Worker that serves those assets * * Docs: * - https://developers.cloudflare.com/workers/static-assets/direct-upload * * Prerequisites: * 1. Generate an API token: https://developers.cloudflare.com/fundamentals/api/get-started/create-token/ * 2. Find your account ID: https://developers.cloudflare.com/fundamentals/setup/find-account-and-zone-ids/ * 3. Find your workers.dev subdomain: https://developers.cloudflare.com/workers/configuration/routing/workers-dev/ * * Environment variables: * - CLOUDFLARE_API_TOKEN (required) * - CLOUDFLARE_ACCOUNT_ID (required) * - ASSETS_DIRECTORY (required) * - CLOUDFLARE_SUBDOMAIN (optional) * * Usage: * Place your static files in the ASSETS_DIRECTORY, then run this script. * Assets will be available at: my-script-with-assets.$subdomain.workers.dev/$filename */ import crypto from 'crypto'; import fs from 'fs'; import { readFile } from 'node:fs/promises'; import { extname } from 'node:path'; import path from 'path'; import { exit } from 'node:process'; import Cloudflare from 'cloudflare'; interface Config { apiToken: string; accountId: string; assetsDirectory: string; subdomain: string | undefined; workerName: string; } interface AssetManifest { [path: string]: { hash: string; size: number; }; } interface UploadPayload { [hash: string]: string; // base64 encoded content } const WORKER_NAME = 'my-worker-with-assets'; const SCRIPT_FILENAME = `${WORKER_NAME}.mjs`; function loadConfig(): Config { const apiToken = process.env['CLOUDFLARE_API_TOKEN']; if (!apiToken) { throw new Error('Missing required environment variable: CLOUDFLARE_API_TOKEN'); } const accountId = process.env['CLOUDFLARE_ACCOUNT_ID']; if (!accountId) { throw new Error('Missing required environment variable: CLOUDFLARE_ACCOUNT_ID'); } const assetsDirectory = process.env['ASSETS_DIRECTORY']; if (!assetsDirectory) { throw new Error('Missing required environment variable: ASSETS_DIRECTORY'); } if (!fs.existsSync(assetsDirectory)) { throw new Error(`Assets directory does not exist: ${assetsDirectory}`); } const subdomain = process.env['CLOUDFLARE_SUBDOMAIN']; return { apiToken, accountId, assetsDirectory, subdomain: subdomain || undefined, workerName: WORKER_NAME, }; } const config = loadConfig(); const client = new Cloudflare({ apiToken: config.apiToken, }); /** * Recursively reads all files from a directory and creates a manifest * mapping file paths to their hash and size. */ function createManifest(directory: string): AssetManifest { const manifest: AssetManifest = {}; function processDirectory(currentDir: string, basePath = ''): void { try { const entries = fs.readdirSync(currentDir, { withFileTypes: true }); for (const entry of entries) { const fullPath = path.join(currentDir, entry.name); const relativePath = path.join(basePath, entry.name); if (entry.isDirectory()) { processDirectory(fullPath, relativePath); } else if (entry.isFile()) { try { const fileContent = fs.readFileSync(fullPath); const extension = extname(relativePath).substring(1); // Generate a hash for the file const hash = crypto .createHash('sha256') .update(fileContent.toString('base64') + extension) .digest('hex') .slice(0, 32); // Normalize path separators to forward slashes const manifestPath = `/${relativePath.replace(/\\/g, '/')}`; manifest[manifestPath] = { hash, size: fileContent.length, }; console.log(`Added to manifest: ${manifestPath} (${fileContent.length} bytes)`); } catch (error) { console.warn(`Failed to process file ${fullPath}:`, error); } } } } catch (error) { throw new Error(`Failed to read directory ${currentDir}: ${error}`); } } processDirectory(directory); if (Object.keys(manifest).length === 0) { throw new Error(`No files found in assets directory: ${directory}`); } console.log(`Created manifest with ${Object.keys(manifest).length} files`); return manifest; } /** * Generates the Worker script content that serves static assets */ function generateWorkerScript(exampleFile: string): string { return ` export default { async fetch(request, env, ctx) { const url = new URL(request.url); // Serve a simple index page at the root if (url.pathname === '/') { return new Response( \` Static Assets Worker

This Worker serves static assets!

To access your assets, add /filename to the URL.

Try visiting \${url.origin}/${exampleFile}">/${exampleFile}

\`, { status: 200, headers: { 'Content-Type': 'text/html' } } ); } // Serve static assets for all other paths return env.ASSETS.fetch(request); } }; `.trim(); } /** * Creates upload payloads from buckets and manifest */ async function createUploadPayloads( buckets: string[][], manifest: AssetManifest, assetsDirectory: string ): Promise { const payloads: UploadPayload[] = []; for (const bucket of buckets) { const payload: UploadPayload = {}; for (const hash of bucket) { // Find the file path for this hash const manifestEntry = Object.entries(manifest).find( ([_, data]) => data.hash === hash ); if (!manifestEntry) { throw new Error(`Could not find file for hash: ${hash}`); } const [relativePath] = manifestEntry; const fullPath = path.join(assetsDirectory, relativePath); try { const fileContent = await readFile(fullPath); payload[hash] = fileContent.toString('base64'); console.log(`Prepared for upload: ${relativePath}`); } catch (error) { throw new Error(`Failed to read file ${fullPath}: ${error}`); } } payloads.push(payload); } return payloads; } /** * Uploads asset payloads */ async function uploadAssets( payloads: UploadPayload[], uploadJwt: string, accountId: string ): Promise { let completionJwt: string | undefined; console.log(`Uploading ${payloads.length} payload(s)...`); for (let i = 0; i < payloads.length; i++) { const payload = payloads[i]!; console.log(`Uploading payload ${i + 1}/${payloads.length}...`); try { const response = await client.workers.assets.upload.create( { account_id: accountId, base64: true, body: payload, }, { headers: { Authorization: `Bearer ${uploadJwt}` }, } ); if (response?.jwt) { completionJwt = response.jwt; } } catch (error) { throw new Error(`Failed to upload payload ${i + 1}: ${error}`); } } if (!completionJwt) { throw new Error('Upload completed but no completion JWT received'); } console.log('✅ All assets uploaded successfully'); return completionJwt; } async function main(): Promise { try { console.log('🚀 Starting Worker creation and deployment with static assets...'); console.log(`📁 Assets directory: ${config.assetsDirectory}`); console.log('📝 Creating asset manifest...'); const manifest = createManifest(config.assetsDirectory); const exampleFile = Object.keys(manifest)[0]?.replace(/^\//, '') || 'file.txt'; const scriptContent = generateWorkerScript(exampleFile); let worker; try { worker = await client.workers.beta.workers.get(config.workerName, { account_id: config.accountId, }); console.log(`♻️ Worker ${config.workerName} already exists. Using it.`); } catch (error) { if (!(error instanceof Cloudflare.NotFoundError)) { throw error; } console.log(`✏️ Creating Worker ${config.workerName}...`); worker = await client.workers.beta.workers.create({ account_id: config.accountId, name: config.workerName, subdomain: { enabled: config.subdomain !== undefined, }, observability: { enabled: true, }, }); } console.log(`⚙️ Worker id: ${worker.id}`); console.log('🔄 Starting asset upload session...'); const uploadResponse = await client.workers.scripts.assets.upload.create( config.workerName, { account_id: config.accountId, manifest, } ); const { buckets, jwt: uploadJwt } = uploadResponse; if (!uploadJwt || !buckets) { throw new Error('Failed to start asset upload session'); } let completionJwt: string; if (buckets.length === 0) { console.log('✅ No new assets to upload!'); // Use the initial upload JWT as completion JWT when no uploads are needed completionJwt = uploadJwt; } else { const payloads = await createUploadPayloads( buckets, manifest, config.assetsDirectory ); completionJwt = await uploadAssets( payloads, uploadJwt, config.accountId ); } console.log('✏️ Creating Worker version...'); // Create a new version with assets const version = await client.workers.beta.workers.versions.create(worker.id, { account_id: config.accountId, main_module: SCRIPT_FILENAME, compatibility_date: new Date().toISOString().split('T')[0]!, bindings: [ { type: 'assets', name: 'ASSETS', }, ], assets: { jwt: completionJwt, }, modules: [ { name: SCRIPT_FILENAME, content_type: 'application/javascript+module', content_base64: Buffer.from(scriptContent).toString('base64'), }, ], }); console.log('🚚 Creating Worker deployment...'); // Create a deployment and point all traffic to the version we created await client.workers.scripts.deployments.create(config.workerName, { account_id: config.accountId, strategy: 'percentage', versions: [ { percentage: 100, version_id: version.id, }, ], }); console.log('✅ Deployment successful!'); if (config.subdomain) { console.log(` 🌍 Your Worker is live! 📍 Base URL: https://${config.workerName}.${config.subdomain}.workers.dev/ 📄 Try accessing: https://${config.workerName}.${config.subdomain}.workers.dev/${exampleFile} `); } else { console.log(` ⚠️ Set up a route, custom domain, or workers.dev subdomain to access your Worker. Add CLOUDFLARE_SUBDOMAIN to your environment variables to set one up automatically. `); } } catch (error) { console.error('❌ Deployment failed:', error); exit(1); } } main(); ``` Explain Code ```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/static-assets/","name":"Static Assets"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/static-assets/direct-upload/","name":"Direct Uploads"}}]} ``` --- --- title: Get Started description: Run front-end websites — static or dynamic — directly on Cloudflare's global network. image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/static-assets/get-started.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Get Started For most front-end applications, you'll want to use a framework. Workers supports number of popular [frameworks](https://developers.cloudflare.com/workers/framework-guides/) that come with ready-to-use components, a pre-defined and structured architecture, and community support. View [framework specific guides](https://developers.cloudflare.com/workers/framework-guides/) to get started using a framework. Alternatively, you may prefer to build your website from scratch if: * You're interested in learning by implementing core functionalities on your own. * You're working on a simple project where you might not need a framework. * You want to optimize for performance by minimizing external dependencies. * You require complete control over every aspect of the application. * You want to build your own framework. This guide will instruct you through setting up and deploying a static site or a full-stack application without a framework on Workers. ## Deploy a static site This guide will instruct you through setting up and deploying a static site on Workers. ### 1\. Create a new Worker project using the CLI [C3 (create-cloudflare-cli) ↗](https://github.com/cloudflare/workers-sdk/tree/main/packages/create-cloudflare) is a command-line tool designed to help you set up and deploy new applications to Cloudflare. Open a terminal window and run C3 to create your Worker project: npm yarn pnpm ``` npm create cloudflare@latest -- my-static-site ``` ``` yarn create cloudflare my-static-site ``` ``` pnpm create cloudflare@latest my-static-site ``` For setup, select the following options: * For _What would you like to start with?_, choose `Hello World example`. * For _Which template would you like to use?_, choose `Static site`. * For _Which language do you want to use?_, choose `TypeScript`. * For _Do you want to use git for version control?_, choose `Yes`. * For _Do you want to deploy your application?_, choose `No` (we will be making some changes before deploying). After setting up your project, change your directory by running the following command: Terminal window ``` cd my-static-site ``` ### 2\. Develop locally After you have created your Worker, run the [wrangler dev](https://developers.cloudflare.com/workers/wrangler/commands/general/#dev) in the project directory to start a local server. This will allow you to preview your project locally during development. Terminal window ``` npx wrangler dev ``` ### 3\. Deploy your project Your project can be deployed to a `*.workers.dev` subdomain or a [Custom Domain](https://developers.cloudflare.com/workers/configuration/routing/custom-domains/), from your own machine or from any CI/CD system, including [Cloudflare's own](https://developers.cloudflare.com/workers/ci-cd/builds/). The [wrangler deploy](https://developers.cloudflare.com/workers/wrangler/commands/general/#deploy) will build and deploy your project. If you're using CI, ensure you update your ["deploy command"](https://developers.cloudflare.com/workers/ci-cd/builds/configuration/#build-settings) configuration appropriately. Terminal window ``` npx wrangler deploy ``` Note Learn about how assets are configured and how routing works from [Routing configuration](https://developers.cloudflare.com/workers/static-assets/routing/). ## Deploy a full-stack application This guide will instruct you through setting up and deploying dynamic and interactive server-side rendered (SSR) applications on Cloudflare Workers. When building a full-stack application, you can use any [Workers bindings](https://developers.cloudflare.com/workers/runtime-apis/bindings/), [including assets' own](https://developers.cloudflare.com/workers/static-assets/binding/), to interact with resources on the Cloudflare Developer Platform. ### 1\. Create a new Worker project [C3 (create-cloudflare-cli) ↗](https://github.com/cloudflare/workers-sdk/tree/main/packages/create-cloudflare) is a command-line tool designed to help you set up and deploy new applications to Cloudflare. Open a terminal window and run C3 to create your Worker project: npm yarn pnpm ``` npm create cloudflare@latest -- my-dynamic-site ``` ``` yarn create cloudflare my-dynamic-site ``` ``` pnpm create cloudflare@latest my-dynamic-site ``` For setup, select the following options: * For _What would you like to start with?_, choose `Hello World example`. * For _Which template would you like to use?_, choose `SSR / full-stack app`. * For _Which language do you want to use?_, choose `TypeScript`. * For _Do you want to use git for version control?_, choose `Yes`. * For _Do you want to deploy your application?_, choose `No` (we will be making some changes before deploying). After setting up your project, change your directory by running the following command: Terminal window ``` cd my-dynamic-site ``` ### 2\. Develop locally After you have created your Worker, run the [wrangler dev](https://developers.cloudflare.com/workers/wrangler/commands/general/#dev) in the project directory to start a local server. This will allow you to preview your project locally during development. Terminal window ``` npx wrangler dev ``` ### 3\. Modify your Project With your new project generated and running, you can begin to write and edit your project: * The `src/index.ts` file is populated with sample code. Modify its content to change the server-side behavior of your Worker. * The `public/index.html` file is populated with sample code. Modify its content, or anything else in `public/`, to change the static assets of your Worker. Then, save the files and reload the page. Your project's output will have changed based on your modifications. ### 4\. Deploy your Project Your project can be deployed to a `*.workers.dev` subdomain or a [Custom Domain](https://developers.cloudflare.com/workers/configuration/routing/custom-domains/), from your own machine or from any CI/CD system, including [Cloudflare's own](https://developers.cloudflare.com/workers/ci-cd/builds/). The [wrangler deploy](https://developers.cloudflare.com/workers/wrangler/commands/general/#deploy) will build and deploy your project. If you're using CI, ensure you update your ["deploy command"](https://developers.cloudflare.com/workers/ci-cd/builds/configuration/#build-settings) configuration appropriately. Terminal window ``` npx wrangler deploy ``` Note Learn about how assets are configured and how routing works from [Routing configuration](https://developers.cloudflare.com/workers/static-assets/routing/). ```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/static-assets/","name":"Static Assets"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/static-assets/get-started/","name":"Get Started"}}]} ``` --- --- title: Headers description: When serving static assets, Workers will attach some headers to the response by default. These are: image: https://developers.cloudflare.com/dev-products-preview.png --- [Skip to content](#%5Ftop) Was this helpful? YesNo [ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/workers/static-assets/headers.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) Copy page # Headers ## Default headers When serving static assets, Workers will attach some headers to the response by default. These are: * **`Content-Type`** A `Content-Type` header is attached to the response if one is provided during [the asset upload process](https://developers.cloudflare.com/workers/static-assets/direct-upload/). [Wrangler](https://developers.cloudflare.com/workers/wrangler/commands/general/#deploy) automatically determines the MIME type of the file, based on its extension. * **`Cache-Control: public, max-age=0, must-revalidate`** Sent when the request does not have an `Authorization` or `Range` header, this response header tells the browser that the asset can be cached, but that the browser should revalidate the freshness of the content every time before using it. This default behavior ensures good website performance for static pages, while still guaranteeing that stale content will never be served. * **`ETag`** This header complements the default `Cache-Control` header. Its value is a hash of the static asset file, and browsers can use this in subsequent requests with an `If-None-Match` header to check for freshness, without needing to re-download the entire file in the case of a match. * **`CF-Cache-Status`** This header indicates whether the asset was served from the cache (`HIT`) or not (`MISS`).[1](#user-content-fn-1) Cloudflare reserves the right to attach new headers to static asset responses at any time in order to improve performance or harden the security of your Worker application. ## Custom headers The default response headers served on static asset responses can be overridden, removed, or added to, by creating a plain text file called `_headers` without a file extension, in the static asset directory of your project. This file will not itself be served as a static asset, but will instead be parsed by Workers and its rules will be applied to static asset responses. If you are using a framework, you will often have a directory named `public/` or `static/`, and this usually contains deploy-ready assets, such as favicons, `robots.txt` files, and site manifests. These files get copied over to a final output directory during the build, so this is the perfect place to author your `_headers` file. If you are not using a framework, the `_headers` file can go directly into your [static assets directory](https://developers.cloudflare.com/workers/static-assets/binding/#directory). Headers defined in the `_headers` file override what Cloudflare ordinarily sends. Warning Custom headers defined in the `_headers` file are not applied to responses generated by your Worker code, even if the request URL matches a rule defined in `_headers`. If you use a server-side rendered (SSR) framework, have configured `assets.run_worker_first`, or otherwise use a Worker script, you will likely need to attach any custom headers you wish to apply directly within that Worker script. ### Attach a header Header rules are defined in multi-line blocks. The first line of a block is the URL or URL pattern where the rule's headers should be applied. On the next line, an indented list of header names and header values must be written: ``` [url] [name]: [value] ``` Using absolute URLs is supported, though be aware that absolute URLs must begin with `https` and specifying a port is not supported. `_headers` rules ignore the incoming request's port and protocol when matching against an incoming request. For example, a rule like `https://example.com/path` would match against requests to `other://example.com:1234/path`. You can define as many `[name]: [value]` pairs as you require on subsequent lines. For example: ``` # This is a comment /secure/page X-Frame-Options: DENY X-Content-Type-Options: nosniff Referrer-Policy: no-referrer /static/* Access-Control-Allow-Origin: * X-Robots-Tag: nosnippet https://myworker.mysubdomain.workers.dev/* X-Robots-Tag: noindex ``` Explain Code An incoming request which matches multiple rules' URL patterns will inherit all rules' headers. Using the previous `_headers` file, the following requests will have the following headers applied: | Request URL | Headers | | ---------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- | | https://custom.domain/secure/page | X-Frame-Options: DENY X-Content-Type-Options: nosniff Referrer-Policy: no-referrer | | https://custom.domain/static/image.jpg | Access-Control-Allow-Origin: \* X-Robots-Tag: nosnippet | | https://myworker.mysubdomain.workers.dev/home | X-Robots-Tag: noindex | | https://myworker.mysubdomain.workers.dev/secure/page | X-Frame-Options: DENY X-Content-Type-Options: nosniff Referrer-Policy: no-referrer X-Robots-Tag: noindex | | https://myworker.mysubdomain.workers.dev/static/styles.css | Access-Control-Allow-Origin: \* X-Robots-Tag: nosnippet, noindex | You may define up to 100 header rules. Each line in the `_headers` file has a 2,000 character limit. The entire line, including spacing, header name, and value, counts towards this limit. If a header is applied twice in the `_headers` file, the values are joined with a comma separator. ### Detach a header You may wish to remove a default header or a header which has been added by a more pervasive rule. This can be done by prepending the header name with an exclamation mark and space (`! `). ``` /* Content-Security-Policy: default-src 'self'; /*.jpg ! Content-Security-Policy ``` ### Match a path The same URL matching features that [\_redirects](https://developers.cloudflare.com/workers/static-assets/redirects/) offers is also available to the `_headers` file. Note, however, that redirects are applied before headers, so when a request matches both a redirect and a header, the redirect takes priority. #### Splats When matching, a splat pattern — signified by an asterisk (`*`) — will greedily match all characters. You may only include a single splat in the URL. The matched value can be referenced within the header value as the `:splat` placeholder. #### Placeholders A placeholder can be defined with `:placeholder_name`. A colon (`:`) followed by a letter indicates the start of a placeholder and the placeholder name that follows must be composed of alphanumeric characters and underscores (`:[A-Za-z]\w*`). Every named placeholder can only be referenced once. Placeholders match all characters apart from the delimiter, which when part of the host, is a period (`.`) or a forward-slash (`/`) and may only be a forward-slash (`/`) when part of the path. Similarly, the matched value can be used in the header values with `:placeholder_name`. ``` /movies/:title x-movie-name: You are watching ":title" ``` #### Examples ##### Cross-Origin Resource Sharing (CORS) To enable other domains to fetch every static asset from your Worker, the following can be added to the `_headers` file: ``` /* Access-Control-Allow-Origin: * ``` This applies the \`Access-Control-Allow-Origin\` header to any incoming URL. Note that the CORS specification only allows \`\*\`, \`null\`, or an exact origin as valid \`Access-Control-Allow-Origin\` values — wildcard patterns within origins are not supported. To allow CORS from specific [preview URLs](https://developers.cloudflare.com/workers/configuration/previews/), you will need to handle this dynamically in your Worker code rather than through the \`\_headers\` file. ##### Prevent your workers.dev URLs showing in search results [Google ↗](https://developers.google.com/search/docs/advanced/robots/robots%5Fmeta%5Ftag#directives) and other search engines often support the `X-Robots-Tag` header to instruct its crawlers how your website should be indexed. For example, to prevent your `\*.\*.workers.dev` URLs from being indexed, add the following to your `_headers` file: ``` https://:version.:subdomain.workers.dev/* X-Robots-Tag: noindex ``` ##### Configure custom browser cache behavior If you have a folder of fingerprinted assets (assets which have a hash in their filename), you can configure more aggressive caching behavior in the browser to improve performance for repeat visitors: ``` /static/* Cache-Control: public, max-age=31556952, immutable ``` ##### Harden security for an application Warning If you are server-side rendering (SSR) or using a Worker to generate responses in any other way and wish to attach security headers, the headers should be sent from the Worker's `Response` instead of using a `_headers` file. For example, if you have an API endpoint and want to allow cross-origin requests, you should ensure that your Worker code attaches CORS headers to its responses, including to `OPTIONS` requests. You can prevent click-jacking by informing browsers not to embed your application inside another (for example, with an `