Waiting
Waiting
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);
}
},
};
```
* TypeScript
```ts
export default {
async fetch(request): PromiseWaiting
Waiting
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;
```
* Hono
```ts
import { Hono } from "hono";
import { cors } from "hono/cors";
// The URL for the remote third party API you want to fetch from
// but does not implement CORS
const API_URL = "https://examples.cloudflareworkers.com/demos/demoapi";
// The endpoint you want the CORS reverse proxy to be on
const PROXY_ENDPOINT = "/corsproxy/";
const app = new Hono();
// Demo page handler
app.get("*", async (c) => {
// Only handle non-proxy requests with this handler
if (c.req.path.startsWith(PROXY_ENDPOINT)) {
return next();
}
// Create the demo page HTML
const DEMO_PAGE = `
Waiting
Waiting
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;
```
* Python
```py
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)
async def on_fetch(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'''
Waiting
Waiting
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)
```
* Rust
```rs
use std::{borrow::Cow, collections::HashMap};
use worker::*;
fn raw*html_response(html: &str) -> ResultWaiting
Waiting
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)
}
```
```plaintext
```
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.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 = `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.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 = `This is a demo using Workers geolocation data.
You are located at: ${latitude},${longitude}.
Based off sensor data from ${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`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"""" + timezone + "
" + timezone + "
${timezone}
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 = `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", }, }); }, }; ``` * TypeScript ```ts export default { async fetch(request): PromiseColo: " + 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 = `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; ``` * Python ```py from workers import Response async def on_fetch(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"""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) ``` * Hono ```ts 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`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; ```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+CAngLzbPYZb6HbW5QDGU2AAwBmABwAmAIwBOQQDZJgyQFYAXCxZtgHOFxp8BIiTPmKVAWABQAYXRUIAU3vYAIlADOMdO6jQ7qki08AmISKjhgBwYAIigaBwAPADoAK3do0lQoMCcIqNj45LToq1t7JwhsABU6GAcAuBgYMD4CKDtkFLgANzh3XgRYCABqYHRccAcrK0SvJBJcB1Q4cAgSAG9LEhI+uipeQIcIXgALAAoEBwBHEAd3CABKDa3tkl47e5ITiGAwEgYSAADAA8AEIXAB5axVACaAAUAKJfH5gAB8L22wIouDo6Ner2BJ0kqIAEg4wGB0CQAOqYMC4YHIIl4-EkYEwVFVE4eEjARAAaxAMBIAHc+iQAOZOBwIAgOXDkOg7EjWSkgXCoMCIBw0zD8mVJRkcjFs5DY3GAoiWE2XCAgBBUMIOEUkABKdy8VHcDjO31+ABpnqyvg44IsEO4Aptg9tou9ys4ILUHNEAtFHAkUH6wERTohvRAGABVKoAMWwomi-pN2wAvtX8bWHla69Xa0QrBpmFodHoePwhGIpLIFEplKU7I5nG5PN5fO0qAEgjpSOFIjFIoQdBlAtlcuvomRKWQSjZJxVqsmGk0Wrw2h00nZppZ1tE+XEAPpjCY5VMFRZFOktadl2PYhH2BiDsYI5mMozBAA) * TypeScript ```ts export default { async fetch(request): PromiseThis markup was generated by a Cloudflare Worker.
`; return new Response(html, { headers: { "content-type": "text/html;charset=UTF-8", }, }); }, } satisfies ExportedHandler; ``` * Python ```py from workers import Response def on_fetch(request): html = """This markup was generated by a Cloudflare Worker.
""" headers = {"content-type": "text/html;charset=UTF-8"} return Response(html, headers=headers) ``` * Rust ```rs use worker::*; #[event(fetch)] async fn fetch(_req: Request, _env: Env, _ctx: Context) -> ResultThis markup was generated by a Cloudflare Worker.
"#; Response::from_html(html) } ``` * Hono ```ts import { Hono } from "hono"; import { html } from "hono/html"; const app = new Hono(); app.get("*", (c) => { const doc = html`This markup was generated by a Cloudflare Worker with Hono.
`; return c.html(doc); }); export default app; ```
// wrangler.jsonc
{
"name": "app-name-goes-here", // name of the app
"main": "src/index.ts", // default file
"compatibility_date": "2025-02-11",
"compatibility_flags": ["nodejs_compat"], // Enable Node.js compatibility
"observability": {
// Enable logging by default
"enabled": true,
}
}
import { DurableObject } from "cloudflare:workers";
interface Env {
WEBSOCKET_HIBERNATION_SERVER: DurableObject;
}
// Durable Object
export class WebSocketHibernationServer extends DurableObject {
async fetch(request) {
// Creates two ends of a WebSocket connection.
const webSocketPair = new WebSocketPair();
const [client, server] = Object.values(webSocketPair);
// Calling `acceptWebSocket()` informs the runtime that this WebSocket is to begin terminating
// request within the Durable Object. It has the effect of "accepting" the connection,
// and allowing the WebSocket to send and receive messages.
// Unlike `ws.accept()`, `state.acceptWebSocket(ws)` informs the Workers Runtime that the WebSocket
// is "hibernatable", so the runtime does not need to pin this Durable Object to memory while
// the connection is open. During periods of inactivity, the Durable Object can be evicted
// from memory, but the WebSocket connection will remain open. If at some later point the
// WebSocket receives a message, the runtime will recreate the Durable Object
// (run the `constructor`) and deliver the message to the appropriate handler.
this.ctx.acceptWebSocket(server);
return new Response(null, {
status: 101,
webSocket: client,
});
},
async webSocketMessage(ws: WebSocket, message: string | ArrayBuffer): void | Promise {
// Upon receiving a message from the client, reply with the same message,
// but will prefix the message with "[Durable Object]: " and return the
// total number of connections.
ws.send(
`[Durable Object] message: ${message}, connections: ${this.ctx.getWebSockets().length}`,
);
},
async webSocketClose(ws: WebSocket, code: number, reason: string, wasClean: boolean) void | Promise {
// If the client closes the connection, the runtime will invoke the webSocketClose() handler.
ws.close(code, "Durable Object is closing WebSocket");
},
async webSocketError(ws: WebSocket, error: unknown): void | Promise {
console.error("WebSocket error:", error);
ws.close(1011, "WebSocket error");
}
}
import { DurableObject } from "cloudflare:workers";
interface Env {
ALARM_EXAMPLE: DurableObject;
}
export default {
async fetch(request, env) {
let url = new URL(request.url);
let userId = url.searchParams.get("userId") || crypto.randomUUID();
let id = env.ALARM_EXAMPLE.idFromName(userId);
return await env.ALARM_EXAMPLE.get(id).fetch(request);
},
};
const SECONDS = 1000;
export class AlarmExample extends DurableObject {
constructor(ctx, env) {
this.ctx = ctx;
this.storage = ctx.storage;
}
async fetch(request) {
// If there is no alarm currently set, set one for 10 seconds from now
let currentAlarm = await this.storage.getAlarm();
if (currentAlarm == null) {
this.storage.setAlarm(Date.now() + 10 \_ SECONDS);
}
}
async alarm(alarmInfo) {
// The alarm handler will be invoked whenever an alarm fires.
// You can use this to do work, read from the Storage API, make HTTP calls
// and set future alarms to run using this.storage.setAlarm() from within this handler.
if (alarmInfo?.retryCount != 0) {
console.log("This alarm event has been attempted ${alarmInfo?.retryCount} times before.");
}
// Set a new alarm for 10 seconds from now before exiting the handler
this.storage.setAlarm(Date.now() + 10 \_ SECONDS);
}
}
// src/index.ts
import { Hono } from 'hono'
import { cors } from 'hono/cors'
interface Env {
AUTH_TOKENS: KVNamespace;
}
const app = new Hono<{ Bindings: Env }>()
// Add CORS middleware
app.use('\*', cors())
app.get('/', async (c) => {
try {
// Get token from header or cookie
const token = c.req.header('Authorization')?.slice(7) ||
c.req.header('Cookie')?.match(/auth_token=([^;]+)/)?.[1];
if (!token) {
return c.json({
authenticated: false,
message: 'No authentication token provided'
}, 403)
}
// Check token in KV
const userData = await c.env.AUTH_TOKENS.get(token)
if (!userData) {
return c.json({
authenticated: false,
message: 'Invalid or expired token'
}, 403)
}
return c.json({
authenticated: true,
message: 'Authentication successful',
data: JSON.parse(userData)
})
} catch (error) {
console.error('Authentication error:', error)
return c.json({
authenticated: false,
message: 'Internal server error'
}, 500)
}
})
export default app
// src/producer.ts
interface Env {
REQUEST_QUEUE: Queue;
UPSTREAM_API_URL: string;
UPSTREAM_API_KEY: string;
}
export default {
async fetch(request: Request, env: Env) {
const info = {
timestamp: new Date().toISOString(),
method: request.method,
url: request.url,
headers: Object.fromEntries(request.headers),
};
await env.REQUEST_QUEUE.send(info);
return Response.json({
message: 'Request logged',
requestId: crypto.randomUUID()
});
},
async queue(batch: MessageBatch, env: Env) {
const requests = batch.messages.map(msg => msg.body);
const response = await fetch(env.UPSTREAM_API_URL, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${env.UPSTREAM_API_KEY}`
},
body: JSON.stringify({
timestamp: new Date().toISOString(),
batchSize: requests.length,
requests
})
});
if (!response.ok) {
throw new Error(`Upstream API error: ${response.status}`);
}
}
};
// Postgres.js 3.4.5 or later is recommended
import postgres from "postgres";
export interface Env {
// If you set another name in the Wrangler config file as the value for 'binding',
// replace "HYPERDRIVE" with the variable name you defined.
HYPERDRIVE: Hyperdrive;
}
export default {
async fetch(request, env, ctx): Promise {
console.log(JSON.stringify(env));
// Create a database client that connects to your database via Hyperdrive.
//
// Hyperdrive generates a unique connection string you can pass to
// supported drivers, including node-postgres, Postgres.js, and the many
// ORMs and query builders that use these drivers.
const sql = postgres(env.HYPERDRIVE.connectionString)
try {
// Test query
const results = await sql`SELECT * FROM pg_tables`;
// Clean up the client, ensuring we don't kill the worker before that is
// completed.
ctx.waitUntil(sql.end());
// Return result rows as JSON
return Response.json(results);
} catch (e) {
console.error(e);
return Response.json(
{ error: e instanceof Error ? e.message : e },
{ status: 500 },
);
}
},
} satisfies ExportedHandler;
import { WorkflowEntrypoint, WorkflowStep, WorkflowEvent } from 'cloudflare:workers';
type Env = {
// Add your bindings here, e.g. Workers KV, D1, Workers AI, etc.
MY_WORKFLOW: Workflow;
};
// User-defined params passed to your workflow
type Params = {
email: string;
metadata: Record;
};
export class MyWorkflow extends WorkflowEntrypoint {
async run(event: WorkflowEvent, step: WorkflowStep) {
// Can access bindings on `this.env`
// Can access params on `event.payload`
const files = await step.do('my first step', async () => {
// Fetch a list of files from $SOME_SERVICE
return {
files: [
'doc_7392_rev3.pdf',
'report_x29_final.pdf',
'memo_2024_05_12.pdf',
'file_089_update.pdf',
'proj_alpha_v2.pdf',
'data_analysis_q2.pdf',
'notes_meeting_52.pdf',
'summary_fy24_draft.pdf',
],
};
});
const apiResponse = await step.do('some other step', async () => {
let resp = await fetch('https://api.cloudflare.com/client/v4/ips');
return await resp.json();
});
await step.sleep('wait on something', '1 minute');
await step.do(
'make a call to write that could maybe, just might, fail',
// Define a retry strategy
{
retries: {
limit: 5,
delay: '5 second',
backoff: 'exponential',
},
timeout: '15 minutes',
},
async () => {
// Do stuff here, with access to the state from our previous steps
if (Math.random() > 0.5) {
throw new Error('API call to $STORAGE_SYSTEM failed');
}
},
);
}
}
export default {
async fetch(req: Request, env: Env): Promise {
let url = new URL(req.url);
if (url.pathname.startsWith('/favicon')) {
return Response.json({}, { status: 404 });
}
// Get the status of an existing instance, if provided
let id = url.searchParams.get('instanceId');
if (id) {
let instance = await env.MY_WORKFLOW.get(id);
return Response.json({
status: await instance.status(),
});
}
const data = await req.json()
// Spawn a new instance and return the ID and status
let instance = await env.MY_WORKFLOW.create({
// Define an ID for the Workflow instance
id: crypto.randomUUID(),
// Pass data to the Workflow instance
// Available on the WorkflowEvent
params: data,
});
return Response.json({
id: instance.id,
details: await instance.status(),
});
},
};
interface Env {
USER_EVENTS: AnalyticsEngineDataset;
}
export default {
async fetch(req: Request, env: Env): Promise {
let url = new URL(req.url);
let path = url.pathname;
let userId = url.searchParams.get("userId");
// Write a datapoint for this visit, associating the data with
// the userId as our Analytics Engine 'index'
env.USER_EVENTS.writeDataPoint({
// Write metrics data: counters, gauges or latency statistics
doubles: [],
// Write text labels - URLs, app names, event_names, etc
blobs: [path],
// Provide an index that groups your data correctly.
indexes: [userId],
});
return Response.json({
hello: "world",
});
,
};
import puppeteer from "@cloudflare/puppeteer";
interface Env {
BROWSER_RENDERING: Fetcher;
}
export default {
async fetch(request, env): Promise {
const { searchParams } = new URL(request.url);
let url = searchParams.get("url");
if (url) {
url = new URL(url).toString(); // normalize
const browser = await puppeteer.launch(env.MYBROWSER);
const page = await browser.newPage();
await page.goto(url);
// Parse the page content
const content = await page.content();
// Find text within the page content
const text = await page.$eval("body", (el) => el.textContent);
// Do something with the text
// e.g. log it to the console, write it to KV, or store it in a database.
console.log(text);
// Ensure we close the browser session
await browser.close();
return Response.json({
bodyText: text,
})
} else {
return Response.json({
error: "Please add an ?url=https://example.com/ parameter"
}, { status: 400 })
}
},
} satisfies ExportedHandler;
// src/index.ts
interface Env {
ASSETS: Fetcher;
}
export default {
fetch(request, env) {
const url = new URL(request.url);
if (url.pathname.startsWith("/api/")) {
return Response.json({
name: "Cloudflare",
});
}
return env.ASSETS.fetch(request);
},
} satisfies ExportedHandler;
Build an AI Agent on Cloudflare Workers, using the agents, and the state management and syncing APIs built into the agents.
// src/index.ts
import { Agent, AgentNamespace, Connection, ConnectionContext, getAgentByName, routeAgentRequest, WSMessage } from 'agents';
import { OpenAI } from "openai";
interface Env {
AIAgent: AgentNamespace;
OPENAI_API_KEY: string;
}
export class AIAgent extends Agent {
// Handle HTTP requests with your Agent
async onRequest(request) {
// Connect with AI capabilities
const ai = new OpenAI({
apiKey: this.env.OPENAI_API_KEY,
});
// Process and understand
const response = await ai.chat.completions.create({
model: "gpt-4",
messages: [{ role: "user", content: await request.text() }],
});
return new Response(response.choices[0].message.content);
}
async processTask(task) {
await this.understand(task);
await this.act();
await this.reflect();
}
// Handle WebSockets
async onConnect(connection: Connection) {
await this.initiate(connection);
connection.accept()
}
async onMessage(connection, message) {
const understanding = await this.comprehend(message);
await this.respond(connection, understanding);
}
async evolve(newInsight) {
this.setState({
...this.state,
insights: [...(this.state.insights || []), newInsight],
understanding: this.state.understanding + 1,
});
}
onStateUpdate(state, source) {
console.log("Understanding deepened:", {
newState: state,
origin: source,
});
}
// Scheduling APIs
// An Agent can schedule tasks to be run in the future by calling this.schedule(when, callback, data), where when can be a delay, a Date, or a cron string; callback the function name to call, and data is an object of data to pass to the function.
//
// Scheduled tasks can do anything a request or message from a user can: make requests, query databases, send emails, read+write state: scheduled tasks can invoke any regular method on your Agent.
async scheduleExamples() {
// schedule a task to run in 10 seconds
let task = await this.schedule(10, "someTask", { message: "hello" });
// schedule a task to run at a specific date
let task = await this.schedule(new Date("2025-01-01"), "someTask", {});
// schedule a task to run every 10 seconds
let { id } = await this.schedule("*/10 * * * *", "someTask", { message: "hello" });
// schedule a task to run every 10 seconds, but only on Mondays
let task = await this.schedule("0 0 * * 1", "someTask", { message: "hello" });
// cancel a scheduled task
this.cancelSchedule(task.id);
// Get a specific schedule by ID
// Returns undefined if the task does not exist
let task = await this.getSchedule(task.id)
// Get all scheduled tasks
// Returns an array of Schedule objects
let tasks = this.getSchedules();
// Cancel a task by its ID
// Returns true if the task was cancelled, false if it did not exist
await this.cancelSchedule(task.id);
// Filter for specific tasks
// e.g. all tasks starting in the next hour
let tasks = this.getSchedules({
timeRange: {
start: new Date(Date.now()),
end: new Date(Date.now() + 60 * 60 * 1000),
}
});
}
async someTask(data) {
await this.callReasoningModel(data.message);
}
// Use the this.sql API within the Agent to access the underlying SQLite database
async callReasoningModel(prompt: Prompt) {
interface Prompt {
userId: string;
user: string;
system: string;
metadata: Record;
}
interface History {
timestamp: Date;
entry: string;
}
let result = this.sql`SELECT * FROM history WHERE user = ${prompt.userId} ORDER BY timestamp DESC LIMIT 1000`;
let context = [];
for await (const row of result) {
context.push(row.entry);
}
const client = new OpenAI({
apiKey: this.env.OPENAI_API_KEY,
});
// Combine user history with the current prompt
const systemPrompt = prompt.system || 'You are a helpful assistant.';
const userPrompt = `${prompt.user}\n\nUser history:\n${context.join('\n')}`;
try {
const completion = await client.chat.completions.create({
model: this.env.MODEL || 'o3-mini',
messages: [
{ role: 'system', content: systemPrompt },
{ role: 'user', content: userPrompt },
],
temperature: 0.7,
max_tokens: 1000,
});
// Store the response in history
this
.sql`INSERT INTO history (timestamp, user, entry) VALUES (${new Date()}, ${prompt.userId}, ${completion.choices[0].message.content})`;
return completion.choices[0].message.content;
} catch (error) {
console.error('Error calling reasoning model:', error);
throw error;
}
}
// Use the SQL API with a type parameter
async queryUser(userId: string) {
type User = {
id: string;
name: string;
email: string;
};
// Supply the type paramter to the query when calling this.sql
// This assumes the results returns one or more User rows with "id", "name", and "email" columns
// You do not need to specify an array type (`User[]` or `Array`) as `this.sql` will always return an array of the specified type.
const user = await this.sql`SELECT * FROM users WHERE id = ${userId}`;
return user
}
// Run and orchestrate Workflows from Agents
async runWorkflow(data) {
let instance = await env.MY_WORKFLOW.create({
id: data.id,
params: data,
})
// Schedule another task that checks the Workflow status every 5 minutes...
await this.schedule("*/5 * * * *", "checkWorkflowStatus", { id: instance.id });
}
}
export default {
async fetch(request, env, ctx): Promise {
// Routed addressing
// Automatically routes HTTP requests and/or WebSocket connections to /agents/:agent/:name
// Best for: connecting React apps directly to Agents using useAgent from @cloudflare/agents/react
return (await routeAgentRequest(request, env)) || Response.json({ msg: 'no agent here' }, { status: 404 });
// Named addressing
// Best for: convenience method for creating or retrieving an agent by name/ID.
let namedAgent = getAgentByName(env.AIAgent, 'agent-456');
// Pass the incoming request straight to your Agent
let namedResp = (await namedAgent).fetch(request);
return namedResp;
// Durable Objects-style addressing
// Best for: controlling ID generation, associating IDs with your existing systems,
// and customizing when/how an Agent is created or invoked
const id = env.AIAgent.newUniqueId();
const agent = env.AIAgent.get(id);
// Pass the incoming request straight to your Agent
let resp = await agent.fetch(request);
// return Response.json({ hello: 'visit https://developers.cloudflare.com/agents for more' });
},
} satisfies ExportedHandler;
// client.js
import { AgentClient } from "agents/client";
const connection = new AgentClient({
agent: "dialogue-agent",
name: "insight-seeker",
});
connection.addEventListener("message", (event) => {
console.log("Received:", event.data);
});
connection.send(
JSON.stringify({
type: "inquiry",
content: "What patterns do you see?",
})
);
// app.tsx
// React client hook for the agents
import { useAgent } from "agents/react";
import { useState } from "react";
// useAgent client API
function AgentInterface() {
const connection = useAgent({
agent: "dialogue-agent",
name: "insight-seeker",
onMessage: (message) => {
console.log("Understanding received:", message.data);
},
onOpen: () => console.log("Connection established"),
onClose: () => console.log("Connection closed"),
});
const inquire = () => {
connection.send(
JSON.stringify({
type: "inquiry",
content: "What insights have you gathered?",
})
);
};
return (
);
}
// State synchronization
function StateInterface() {
const [state, setState] = useState({ counter: 0 });
const agent = useAgent({
agent: "thinking-agent",
onStateUpdate: (newState) => setState(newState),
});
const increment = () => {
agent.setState({ counter: state.counter + 1 });
};
return (
Count: {state.counter}
);
}
{
"durable_objects": {
"bindings": [
{
"binding": "AIAgent",
"class_name": "AIAgent"
}
]
},
"migrations": [
{
"tag": "v1",
// Mandatory for the Agent to store state
"new_sqlite_classes": ["AIAgent"]
}
]
}
- Imports the `Agent` class from the `agents` package
- Extends the `Agent` class and implements the methods exposed by the `Agent`, including `onRequest` for HTTP requests, or `onConnect` and `onMessage` for WebSockets.
- Uses the `this.schedule` scheduling API to schedule future tasks.
- Uses the `this.setState` API within the Agent for syncing state, and uses type parameters to ensure the state is typed.
- Uses the `this.sql` as a lower-level query API.
- For frontend applications, uses the optional `useAgent` hook to connect to the Agent via WebSockets
import { OpenAI } from "openai";
interface Env {
OPENAI_API_KEY: string;
}
// Define your JSON schema for a calendar event
const CalendarEventSchema = {
type: 'object',
properties: {
name: { type: 'string' },
date: { type: 'string' },
participants: { type: 'array', items: { type: 'string' } },
},
required: ['name', 'date', 'participants']
};
export default {
async fetch(request: Request, env: Env) {
const client = new OpenAI({
apiKey: env.OPENAI_API_KEY,
// Optional: use AI Gateway to bring logs, evals & caching to your AI requests
// https://developers.cloudflare.com/ai-gateway/providers/openai/
// baseUrl: "https://gateway.ai.cloudflare.com/v1/{account_id}/{gateway_id}/openai"
});
const response = await client.chat.completions.create({
model: 'gpt-4o-2024-08-06',
messages: [
{ role: 'system', content: 'Extract the event information.' },
{ role: 'user', content: 'Alice and Bob are going to a science fair on Friday.' },
],
// Use the `response_format` option to request a structured JSON output
response_format: {
// Set json_schema and provide ra schema, or json_object and parse it yourself
type: 'json_schema',
schema: CalendarEventSchema, // provide a schema
},
});
// This will be of type CalendarEventSchema
const event = response.choices[0].message.parsed;
return Response.json({
"calendar_event": event,
})
}
}
https://example.com/hello fetch request to https://notexample.com/hello.") --> B(Is notexample.com https://notexample.com/ https://notexample.com/hello example.com zone.)
C -- Yes --> E(Do you own notexample.com?)
C -- No --> F(Purge https://notexample.com/hello example.com zone.)
E -- Yes --> G(Purge https://notexample.com/hello notexample.com zone.)
E -- No --> H(Sorry, you can not purge the asset. notexample.com can purge it.)
```
### Purge assets stored with the Cache API
Assets stored in the cache through [Cache API](https://developers.cloudflare.com/workers/runtime-apis/cache/) operations can be purged in a couple of ways:
* Call `cache.delete` within a Worker to invalidate the cache for the asset with a matching request variable.
* Assets purged in this way are only purged locally to the data center the Worker runtime was executed.
* To purge an asset globally, use the standard [cache purge options](https://developers.cloudflare.com/cache/how-to/purge-cache/). Based on cache API implementation, not all cache purge endpoints function for purging assets stored by the Cache API.
* All assets on a zone can be purged by using the [Purge Everything](https://developers.cloudflare.com/cache/how-to/purge-cache/purge-everything/) cache operation. This purge will remove all assets associated with a Cloudflare zone from cache in all data centers regardless of the method set.
* [Cache Tags](https://developers.cloudflare.com/cache/how-to/purge-cache/purge-by-tags/#add-cache-tag-http-response-headers) can be added to requests dynamically in a Worker by calling `response.headers.append()` and appending `Cache-Tag` values dynamically to that request. Once set, those tags can be used to selectively purge assets from cache without invalidating all cached assets on a zone.
* Currently, it is not possible to purge a URL stored through Cache API that uses a custom cache key set by a Worker. Instead, use a [custom key created via Cache Rules](https://developers.cloudflare.com/cache/how-to/cache-rules/settings/#cache-key). Alternatively, purge your assets using purge everything, purge by tag, purge by host or purge by prefix.
## Edge versus browser caching
The browser cache is controlled through the `Cache-Control` header sent in the response to the client (the `Response` instance return from the handler). Workers can customize browser cache behavior by setting this header on the response.
Other means to control Cloudflare’s cache that are not mentioned in this documentation include: Page Rules and Cloudflare cache settings. Refer to the [How to customize Cloudflare’s cache](https://developers.cloudflare.com/cache/concepts/customize-cache/) if you wish to avoid writing JavaScript with still some granularity of control.
What should I use: the Cache API or fetch for caching objects on Cloudflare?
For requests where Workers are behaving as middleware (that is, Workers are sending a subrequest via `fetch`) it is recommended to use `fetch`. This is because preexisting settings are in place that optimize caching while preventing unintended dynamic caching. For projects where there is no backend (that is, the entire project is on Workers as in [Workers Sites](https://developers.cloudflare.com/workers/configuration/sites/start-from-scratch)) the Cache API is the only option to customize caching.
The asset will be cached under the hostname specified within the Worker's subrequest — not the Worker's own hostname. Therefore, in order to purge the cached asset, the purge will have to be performed for the hostname included in the Worker subrequest.
### `fetch`
In the context of Workers, a [`fetch`](https://developers.cloudflare.com/workers/runtime-apis/fetch/) provided by the runtime communicates with the Cloudflare cache. First, `fetch` checks to see if the URL matches a different zone. If it does, it reads through that zone’s cache (or Worker). Otherwise, it reads through its own zone’s cache, even if the URL is for a non-Cloudflare site. Cache settings on `fetch` automatically apply caching rules based on your Cloudflare settings. `fetch` does not allow you to modify or inspect objects before they reach the cache, but does allow you to modify how it will cache.
When a response fills the cache, the response header contains `CF-Cache-Status: HIT`. You can tell an object is attempting to cache if one sees the `CF-Cache-Status` at all.
This [template](https://developers.cloudflare.com/workers/examples/cache-using-fetch/) shows ways to customize Cloudflare cache behavior on a given request using fetch.
### Cache API
The [Cache API](https://developers.cloudflare.com/workers/runtime-apis/cache/) can be thought of as an ephemeral key-value store, whereby the `Request` object (or more specifically, the request URL) is the key, and the `Response` is the value.
There are two types of cache namespaces available to the Cloudflare Cache:
* **`caches.default`** – You can access the default cache (the same cache shared with `fetch` requests) by accessing `caches.default`. This is useful when needing to override content that is already cached, after receiving the response.
* **`caches.open()`** – You can access a namespaced cache (separate from the cache shared with `fetch` requests) using `let cache = await caches.open(CACHE_NAME)`. Note that [`caches.open`](https://developer.mozilla.org/en-US/docs/Web/API/CacheStorage/open) is an async function, unlike `caches.default`.
When to use the Cache API:
* When you want to programmatically save and/or delete responses from a cache. For example, say an origin is responding with a `Cache-Control: max-age:0` header and cannot be changed. Instead, you can clone the `Response`, adjust the header to the `max-age=3600` value, and then use the Cache API to save the modified `Response` for an hour.
* When you want to programmatically access a Response from a cache without relying on a `fetch` request. For example, you can check to see if you have already cached a `Response` for the `https://example.com/slow-response` endpoint. If so, you can avoid the slow request.
This [template](https://developers.cloudflare.com/workers/examples/cache-api/) shows ways to use the cache API. For limits of the cache API, refer to [Limits](https://developers.cloudflare.com/workers/platform/limits/#cache-api-limits).
Tiered caching and the Cache API
Cache API within Workers does not support tiered caching. Tiered Cache concentrates connections to origin servers so they come from a small number of data centers rather than the full set of network locations. Cache API is local to a data center, this means that `cache.match` does a lookup, `cache.put` stores a response, and `cache.delete` removes a stored response only in the cache of the data center that the Worker handling the request is in. Because these methods apply only to local cache, they will not work with tiered cache.
## Related resources
* [Cache API](https://developers.cloudflare.com/workers/runtime-apis/cache/)
* [Customize cache behavior with Workers](https://developers.cloudflare.com/cache/interaction-cloudflare-products/workers/)