TypeScript example: read POST request body with type hints
```ts
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 {
return 'a file';
}
}
```
This example shows typed request body reading for different content types.
Rust example: read POST request body with content-type matching
```rs
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::<Payload>().await.unwrap()),
'text/html' => req.text().await.unwrap(),
'multipart/form-data' => format!('{:?}', req.form_data().await.unwrap()),
_ => String::from('a file'),
}
}
```
This example shows how to match on content-type and read POST bodies in Rust.
Hono framework example: read POST request body and serve forms
```ts
async function readRequestBody(request: Request): Promise<string> {
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('form')) {
const formData = await request.formData();
const body: Record<string, string> = {};
for (const [key, value] of formData.entries()) {
body[key] = value.toString();
}
return JSON.stringify(body);
} else {
return await request.text();
}
}
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);
});
```
This example shows how to handle POST requests and serve HTML forms using the Hono framework.
Return JSON with Response.json() in TypeScript
In TypeScript, use Response.json(data) to return JSON directly from a Worker fetch handler. The method accepts a data object and automatically sets the correct content-type header.
ExportedHandler type for TypeScript Workers
TypeScript Worker exports should be typed with 'satisfies ExportedHandler' to ensure the default export matches the required Worker interface with the async fetch method.
Return JSON with Hono framework
In Hono, use c.json(data) in route handlers to return JSON responses. The context object c provides the json() method for automatic serialization and content-type handling.
Return JSON with Response::from_json in Rust
In Rust, use Response::from_json(&data) to return JSON from a Worker. The data must derive Serialize and Deserialize traits from serde.
Return JSON with response headers in Python
In Python, use json.dumps() to serialize data and return a Response object with the serialized JSON as the body and a headers dictionary containing 'content-type': 'application/json'.
HTMLRewriter basic usage for link rewriting
The HTMLRewriter API allows you to parse and transform HTML. To rewrite links in HTML, create a handler class that implements an element() method to get and set attributes, then instantiate HTMLRewriter and chain .on() calls to specify which elements and handlers to apply. Call .transform(response) to apply the transformations to a Response object.
HTMLRewriter available in Python Workers
HTMLRewriter is available in Python Workers. Import it from the js module, create handler classes with an element() method, and use create_proxy() from pyodide.ffi to wrap Python handler instances before passing them to HTMLRewriter.on() methods.
HTMLRewriter with Hono framework
When using the Hono framework with Workers, instantiate HTMLRewriter inside route handlers and call rewriter.transform(response) on responses. Access the raw request via c.req.raw and ensure you preserve response headers when returning the transformed response.
Check Content-Type before using HTMLRewriter
Always check that the response Content-Type header starts with 'text/html' before calling HTMLRewriter.transform(). If the response is not HTML, pass through the response unchanged, as HTMLRewriter is only designed for HTML content.
HTMLRewriter example: rewrite href and src attributes
This example shows how to replace occurrences of 'developer.mozilla.org' with 'mynewdomain.com' in href attributes of <a> tags and src attributes of <img> tags:
```js
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 (contentType.startsWith("text/html")) {
return rewriter.transform(res);
} else {
return res;
}
```
Stream and transform JSON response example
Fetch a large JSON response, transform fields, and stream it back to the client:
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" },
});
},
};
Use @streamparser/json-whatwg library for streaming JSON
The @streamparser/json-whatwg library provides a streaming JSON parser compatible with the Web Streams API. Install it with: npm install @streamparser/json-whatwg
Stream and parse JSON request body example
Parse a large JSON request body extractively:
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 });
},
};
JSONParser paths option syntax
The JSONParser accepts a paths option that uses JSONPointer syntax to specify which parts of the JSON to parse. For example, "$.users.*" matches all elements in the users array, and "$.items.*" matches all elements in the items array.
Consume prefetched bootstrap data in SPA client code
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. Add a TypeScript type declaration for the global property: `declare global { interface Window { __BOOTSTRAP_DATA__?: unknown; } }`.
Client-side bootstrap data consumption example
```tsx
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 <LoadingSpinner />;
return <Dashboard data={data} />;
}
```
HTMLRewriter inject meta tags for social media crawlers
Use HTMLRewriter to inject Open Graph or other <meta> tags into the <head> based on the request path. This gives social-media crawlers correct previews without requiring a full server-side rendering framework.
HTMLRewriter inject meta tags example
```ts
new HTMLRewriter()
.on("head", {
element(el) {
el.append(`<meta property="og:title" content="${title}" />`, {
html: true,
});
},
})
.transform(shell);
```
HTMLRewriter inject user configuration example
```ts
new HTMLRewriter()
.on("body", {
element(el) {
el.prepend(
`<script>window.__APP_CONFIG__=${JSON.stringify({
apiBase: env.API_BASE_URL,
featureFlags: { darkMode: true },
})}</script>`,
{ html: true },
);
},
})
.transform(shell);
```
HTMLRewriter supports framework-agnostic SPA shell pattern
The HTMLRewriter bootstrap data injection pattern works with any SPA framework including React, Vue, Svelte, and others. The same client-side consumption pattern applies across all frameworks.
HTMLRewriter inject bootstrap data into SPA shell
Use HTMLRewriter to fetch bootstrap API data in parallel with the SPA shell HTML, then inject the serialized data into a <script> tag in the <body>. This allows the SPA to have all necessary data before its JavaScript runs, eliminating client-side data fetching on initial load. If the API call fails, the shell still loads and the SPA falls back to client-side data fetching.
SPA shell bootstrap data injection example with Static Assets
```ts
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const url = new URL(request.url);
// Serve root-level static files 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 <body>.
return new HTMLRewriter()
.on("body", {
async element(el) {
const data = await dataPromise;
if (data) {
el.prepend(
`<script>window.__BOOTSTRAP_DATA__=${JSON.stringify(data)}</script>`,
{ html: true },
);
}
},
})
.transform(shell);
},
} satisfies ExportedHandler<Env>;
async function fetchBootstrapData(
env: Env,
pathname: string,
headers: Headers,
): Promise<unknown | null> {
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;
}
}
```
SPA shell bootstrap data injection example with external origin
```ts
export default {
async fetch(request: Request, env: Env): Promise<Response> {
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 <body>.
return new HTMLRewriter()
.on("body", {
async element(el) {
const data = await dataPromise;
if (data) {
el.prepend(
`<script>window.__BOOTSTRAP_DATA__=${JSON.stringify(data)}</script>`,
{ html: true },
);
}
},
})
.transform(shell);
},
} satisfies ExportedHandler<Env>;
async function fetchBootstrapData(
env: Env,
pathname: string,
headers: Headers,
): Promise<unknown | null> {
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;
}
}
```
Hono middleware pattern for HTMLRewriter with response cloning
When using Hono, apply HTMLRewriter in middleware by cloning the response, reading its body as text, creating a new Response object with the modified body, and assigning it back to c.res. First check the content-type header to ensure it is text/html before processing.
HTMLRewriter element.append() with HTML content
The element.append() method can add HTML content to an element. Pass the HTML string as the first argument and {html: true} as the second argument to interpret the string as HTML rather than plain text. Example: element.append('<script src="..."></script>', {html: true}).
HTMLRewriter element.getAttribute() to match elements
Use element.getAttribute() to retrieve an attribute value and perform conditional transformations. For example, check if element.getAttribute('id') equals a specific value before appending content to that element.
Turnstile script URL
The Turnstile API script is hosted at https://challenges.cloudflare.com/turnstile/v0/api.js and should be loaded with async and defer attributes.
Turnstile widget HTML structure
A Turnstile widget is rendered as a div element with the class 'cf-turnstile' and a data-sitekey attribute containing the site key. Optional attributes include data-theme which can be set to 'light'. Example: <div class="cf-turnstile" data-sitekey="${SITE_KEY}" data-theme="light"></div>.
Turnstile token verification endpoint
Turnstile tokens are verified by sending a POST request to https://challenges.cloudflare.com/turnstile/v0/siteverify with FormData containing the fields: secret (the Turnstile Secret key), response (the token from cf-turnstile-response), and optionally remoteip (the client IP address).
Turnstile token field name in form data
When a Turnstile widget is rendered, it injects a token into the form data under the field name 'cf-turnstile-response'. This token must be retrieved and verified server-side.
Turnstile verification response format
The Turnstile siteverify API returns a JSON response with a 'success' boolean field indicating whether the token was valid. If success is false, the token validation failed and the request should be rejected.
Turnstile implementation requires two parts
Implementing Turnstile in Workers requires two parts: (1) injecting the Turnstile widget into HTML using HTMLRewriter, and (2) verifying the resulting token server-side using the Siteverify API before processing the form submission.
Python Workers HTMLRewriter usage
In Python Workers, use pyodide.ffi.create_proxy() to wrap Python classes as handlers, then pass them to HTMLRewriter.new().on(). Each handler class must implement an element() method that receives the element to transform.
HTMLRewriter API basic usage pattern
HTMLRewriter is a runtime API that allows matching and transforming specific HTML elements in a response. Use the .on() method to attach an element handler for a specific tag, then call .transform(response) to apply the transformations. For example: new HTMLRewriter().on('head', { element(element) { /* handler */ } }).transform(res).
Route preloading with Speculation Rules API
When preload: true is set on a static mount route, the router automatically preloads those routes to enable faster navigation. For Chromium-based browsers (Chrome, Edge, Opera, Brave), the router uses the Speculation Rules API, a modern browser-native prefetching mechanism that injects <script type="speculationrules"> into the <head> element. The browser handles prefetching automatically with optimal priority management, respects user preferences (battery saver, data saver modes), uses per-document in-memory cache for faster access, is not blocked by Cache-Control headers, and is more efficient than JavaScript-based fetching.
JavaScript module export syntax required
A Worker must use 'export default' JavaScript syntax to define a JavaScript module. The exported object contains properties corresponding to the events your Worker should handle, such as the fetch handler.
Modular Python Workers
Python Workers can be split across multiple files using standard Python import statements. When entry point files are edited, pywrangler automatically detects changes and reloads the Worker.
Cron trigger in Python Worker
Implement an async scheduled(self, controller, env, ctx) method in a WorkerEntrypoint subclass to handle cron triggers. All four parameters (self, controller, env, ctx) are required for scheduled methods, unlike fetch() which only requires self and request.
Read bundled asset files in Python Worker
Use pathlib.Path to read bundled asset files. Get the file path relative to the current module using Path(__file__).parent / "filename". For example: html_file = Path(__file__).parent / "file.html"; return Response(html_file.read_text(), headers={"Content-Type": "text/html"}).
Parse URL and query parameters in Python Worker
Use urllib.parse.urlparse to parse the request URL and urllib.parse.parse_qs to extract query parameters into a Python dictionary. Access the URL path via url.path and query parameters via params. For example: url = urlparse(request.url); params = parse_qs(url.query); name = params["name"][0].
Import local modules in Python Worker
To import a local module in a Python Worker, use the module name directly without specifying the directory. For example, if your Worker has src/main.py and src/module.py, use `import module` in main.py. The main module path is specified in wrangler.toml with `main = "src/main.py"`, and the src directory does not need to be included in the import statement.
Workflow steps in Python Worker
Extend WorkflowEntrypoint and implement async run(self, event, step) method. Define workflow steps using @step.do() decorator on async functions. Pass concurrent=True to @step.do(concurrent=True) to run steps concurrently. Pass step function results as parameters to dependent steps. For example: @step.do(); async def step_a(): return 10; @step.do(concurrent=True); async def final_step(step_a): return step_a.
Python Worker basic structure with WorkerEntrypoint
A basic Python Worker includes a Python file with a Default class extending WorkerEntrypoint that implements an async fetch method. Example: from workers import Response, WorkerEntrypoint; class Default(WorkerEntrypoint): async def fetch(self, request): return Response("Hello world!")
addEventListener handleEvent function support
As of 2021-09-24, addEventListener supports listeners passed as either a function or an object with a handleEvent member function.
WebSocket.close arguments optional
As of 2021-04-29, the arguments to WebSocket.close() are now optional, as the standard requires.
WebCrypto wrapKey/unwrapKey for AES
As of 2021-04-29, WebCrypto implements wrapKey() and unwrapKey() for AES algorithms.
WebCrypto HKDF implementation
As of 2021-05-14, WebCrypto implements HKDF.
WebCrypto RSA-OAEP support
As of 2021-05-14, WebCrypto adds support for RSA-OAEP.
WebCrypto JWK export for RSA, ECDSA, ECDH
As of 2021-05-14, WebCrypto supports JWK export for RSA, ECDSA, and ECDH key types.
WebCrypto ECDSA/ECDH raw format
As of 2021-06-04, WebCrypto supports raw import/export format for ECDSA/ECDH public keys.
WebCrypto Ed25519 support
As of 2021-06-27, WebCrypto implements non-standard Ed25519 operation (algorithm NODE-ED25519, curve name NODE-ED25519). Raw import/export of private keys is disallowed, per parity with ECDSA/ECDH.
DOMException constructor string coercion
As of 2022-11-30, the DOMException constructor has been updated so that the message and name arguments can be any JavaScript value coercible into a string, not just strings.
TransformStream standard constructor support
As of 2022-06-03, it is possible to create standard TransformStream instances that perform transformations on data. The transformstream_enable_standard_constructor compatibility flag is required to enable this, as it changes the behavior of the default new TransformStream() with no arguments.
ReadableStream pipeTo and pipeThrough AbortSignal support
As of 2022-05-19, ReadableStream pipeTo and pipeThrough now support cancellation using AbortSignal.
AES-GCM zero-length IV error message
As of 2022-04-08, the AES-GCM implementation of the Web Cryptography API returns a friendlier error explaining that 0-length IVs are not allowed.
IdentityTransformStream for byte-oriented transforms
As of 2022-03-24, IdentityTransformStream creates a byte-oriented TransformStream that passes bytes through unmodified. The readable half supports BYOB-reads. It is identical to the current non-spec-compliant TransformStream implementation. New code should use IdentityTransformStream instead of new TransformStream() to avoid breaking changes.
ByteLengthQueuingStrategy and CountQueuingStrategy availability
As of 2022-03-17, the standard ByteLengthQueuingStrategy and CountQueuingStrategy classes are available.