# Adding New Experiments (/docs/adding-experiments) This guide walks you through the process of adding a new experiment to the Cloudflare Experiments repository. ## Before You Start [#before-you-start] Always open an issue using the [Experiment idea template](https://github.com/shrinathsnayak/cloudflare-experiments/issues/new) to discuss your experiment with maintainers before implementing it. ### What Makes a Good Experiment? [#what-makes-a-good-experiment] A good experiment: * Demonstrates a **specific Cloudflare capability** (Workers AI, Browser Rendering, D1, KV, R2, etc.) * Solves a **real developer need** (not just "Hello World") * Is **edge-first** and runs in under 60 seconds * Has a **single responsibility** (one experiment = one capability) * Is **independently deployable** without dependencies on other experiments * Provides **practical value** that developers would actually use ## Step-by-Step Process [#step-by-step-process] ### Open an experiment idea issue [#open-an-experiment-idea-issue] Use the [Experiment idea template](https://github.com/shrinathsnayak/cloudflare-experiments/issues/new) to propose your experiment. Include: * What Cloudflare capability it demonstrates * What problem it solves * Basic description of how it works * Any special bindings or resources needed (D1, KV, AI, etc.) Wait for maintainer feedback before proceeding. ### Create the experiment directory [#create-the-experiment-directory] Create a new directory under `apps/experiments//`: ```bash mkdir -p apps/experiments/my-experiment cd apps/experiments/my-experiment ``` Use kebab-case for the experiment name (e.g. `is-it-down`, `link-shortener`). ### Set up the project structure [#set-up-the-project-structure] Create the standard directory structure: ### Initialize package.json [#initialize-packagejson] Create a `package.json` with the required dependencies: ```json { "name": "my-experiment", "version": "1.0.0", "type": "module", "scripts": { "dev": "wrangler dev", "deploy": "wrangler deploy" }, "dependencies": { "hono": "^4.0.0" }, "devDependencies": { "@cloudflare/workers-types": "^4.0.0", "typescript": "^5.0.0", "wrangler": "^3.0.0" } } ``` Then install dependencies: ```bash npm install ``` ### Create wrangler.toml [#create-wranglertoml] Configure your Worker with `wrangler.toml`: ```toml name = "my-experiment" main = "src/index.ts" compatibility_date = "2024-01-01" # Add bindings as needed: # [[d1_databases]] # binding = "DB" # database_name = "my-database" # database_id = "..." # [[kv_namespaces]] # binding = "MY_KV" # id = "..." ``` Or use `wrangler.json` if you prefer JSON format. ### Set up TypeScript configuration [#set-up-typescript-configuration] Create `tsconfig.json`: ```json { "compilerOptions": { "target": "ES2022", "module": "ES2022", "lib": ["ES2022"], "types": ["@cloudflare/workers-types"], "strict": true, "moduleResolution": "node", "skipLibCheck": true, "esModuleInterop": true, "resolveJsonModule": true }, "include": ["src/**/*"], "exclude": ["node_modules"] } ``` ### Implement the Worker [#implement-the-worker] Follow the [code standards](/code-standards) to implement your Worker: **Create `src/types/env.d.ts`:** ```typescript /// export interface Env { // Add your bindings here // DB: D1Database; // MY_KV: KVNamespace; } ``` **Create `src/utils/response.ts`:** ```typescript import type { Context } from "hono"; export function jsonError(c: Context, message: string, code: string, status: 400 | 404 | 502 = 400) { return c.json({ error: message, code }, { status }); } export function jsonSuccess(c: Context, data: T, status: 200 = 200) { return c.json(data, { status }); } ``` **Create `src/lib/url.ts` (if handling URL params):** ```typescript /** * Validates and normalizes a URL for safe fetching. * Returns the URL string or null if invalid/not allowed. */ export function validateUrl(input: string | undefined): string | null { if (!input || typeof input !== "string") return null; const trimmed = input.trim(); if (!trimmed) return null; try { const url = new URL(trimmed); if (url.protocol !== "http:" && url.protocol !== "https:") { return null; } return url.href; } catch { return null; } } ``` **Create your route handlers in `src/routes/`:** ```typescript import { Hono } from "hono"; import type { Env } from "../types/env"; import { jsonError, jsonSuccess } from "../utils/response"; const app = new Hono<{ Bindings: Env }>(); app.get("/my-route", async (c) => { // Your logic here return jsonSuccess(c, { message: "Success" }); }); export default app; ``` **Create `src/index.ts`:** ```typescript import { Hono } from "hono"; import type { Env } from "./types/env"; import myRoutes from "./routes/my-route"; const app = new Hono<{ Bindings: Env }>(); app.route("/", myRoutes); app.get("/", (c) => { return c.json({ name: "my-experiment", description: "What this experiment does", usage: "GET /my-route", }); }); app.onError((err, c) => { return c.json({ error: err.message, code: "INTERNAL_ERROR" }, 500); }); export default { fetch: app.fetch, }; ``` ### Test locally [#test-locally] Run your experiment locally: ```bash npm run dev ``` Test your endpoints: ```bash curl http://localhost:8787/my-route ``` Verify: * All routes work as expected * Error handling works correctly * TypeScript has no errors ### Create README.md [#create-readmemd] Document your experiment with a comprehensive README: ````markdown # My Experiment Brief description of what this experiment does. ## Features - Feature 1 - Feature 2 ## API ### `GET /my-route` | Query | Required | Description | | ----- | -------- | ----------- | | `param` | Yes | Description | **Example** ```http GET /my-route?param=value ```` **Response** ```json { "result": "data" } ``` **Errors** * `400` - Invalid parameter ## Run locally [#run-locally] ```bash cd apps/experiments/my-experiment npm install npm run dev ``` ## Deploy [#deploy] [![Deploy to Cloudflare Workers](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/YOUR_USERNAME/cloudflare-experiments/tree/main/apps/experiments/my-experiment) ## Cloudflare features used [#cloudflare-features-used] * Workers * List relevant features ```` ### Add to root README.md Add a row to the experiments table in the root `README.md`: ```markdown | [My Experiment](apps/experiments/my-experiment/) | Brief description | [Deploy](https://deploy.workers.cloudflare.com/?url=https://github.com/YOUR_USERNAME/cloudflare-experiments/tree/main/apps/experiments/my-experiment) | ```` ### Document on the docs site [#document-on-the-docs-site] Generate the Fumadocs page for the experiment: ```bash node apps/docs/scripts/scaffold-experiment-doc.mjs my-experiment ``` Replace `TODO` sections using `src/routes/`, types, and `wrangler.json`. Add `"experiments/my-experiment"` to `apps/docs/content/docs/meta.json` under the right category. See the [Experiment Documentation Guide](/reference/experiment-docs) for structure, MDX conventions, and the full checklist. ### Submit a pull request [#submit-a-pull-request] Create a PR with: * Clear title: "Add \[experiment-name] experiment" * Description linking to the experiment idea issue * Screenshots or examples of the API in action (if applicable) * Confirmation that `npx wrangler deploy` works Fill in the PR template with all required information. ## Common Patterns and Examples [#common-patterns-and-examples] ### Using Reference Experiments [#using-reference-experiments] You can use existing experiments as references: **Reference: `whereami` or `is-it-down`** These experiments: * Don't use persistent storage * Have simple route handlers * Focus on edge networking Good for: Simple APIs, URL validators, fetch-based tools **Reference: `link-shortener`** This experiment: * Uses D1 for primary storage * Uses KV for read cache * Handles database errors gracefully * Implements retry logic for unique constraints Good for: CRUD operations, data persistence, caching patterns **Reference: `ai-website-summary` or `github-repo-explainer`** These experiments: * Use Workers AI binding * Handle streaming responses * Process external content Good for: AI-powered tools, content analysis, summarization **Reference: `screenshot-api`** This experiment: * Uses Browser Rendering API * Handles binary responses (images) * Implements proper timeouts Good for: Screenshot tools, browser automation, PDF generation ### Error Handling Patterns [#error-handling-patterns] **Handling fetch errors:** ```typescript export async function fetchWithTiming(url: string): Promise { const start = Date.now(); const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS); try { const res = await fetch(url, { method: "GET", signal: controller.signal, headers: { "User-Agent": "Cloudflare-Experiments/1.0" }, }); const responseTimeMs = Date.now() - start; clearTimeout(timeoutId); return { ok: res.ok, statusCode: res.status, responseTimeMs, }; } catch (e) { clearTimeout(timeoutId); const responseTimeMs = Date.now() - start; const message = e instanceof Error ? e.message : "Unknown error"; return { ok: false, statusCode: 0, responseTimeMs, error: message, }; } } ``` **Handling JSON parsing errors:** ```typescript let body: RequestBody; try { body = (await c.req.json()) as RequestBody; } catch { return jsonError(c, "Invalid or missing JSON body", "INVALID_BODY"); } ``` **Handling database constraint errors:** ```typescript try { await db.prepare("INSERT INTO table (id, data) VALUES (?, ?)").bind(id, data).run(); return jsonSuccess(c, { id }); } catch (e) { const err = e as { message?: string }; if (err.message?.includes("UNIQUE constraint")) { return jsonError(c, "Record already exists", "DUPLICATE_ENTRY"); } throw e; // Let global error handler deal with it } ``` ## Deployment Checklist [#deployment-checklist] Before submitting your PR, verify: * [ ] `npm run dev` works locally * [ ] `npx wrangler deploy` deploys successfully * [ ] All endpoints return proper JSON responses * [ ] Error codes are descriptive (e.g. `INVALID_URL`, not `ERROR`) * [ ] Global error handler returns consistent 500 responses * [ ] README.md documents all API endpoints * [ ] Bindings are declared in both `wrangler.toml` and `src/types/env.d.ts` * [ ] TypeScript strict mode passes with no errors * [ ] No `any` types in the code * [ ] Added experiment to root README.md table * [ ] Added or updated docs page at `apps/docs/content/docs/experiments/.mdx` and `meta.json` (see [Experiment Documentation Guide](/reference/experiment-docs)) ## Getting Help [#getting-help] Open a [Discussion](https://github.com/shrinathsnayak/cloudflare-experiments/discussions) describing what you want to build. The community can help suggest the right Cloudflare features. Yes, but the experiment should have a single clear purpose. For example, `link-shortener` uses both D1 and KV, but its purpose is clear: shorten URLs. Include the SQL schema in your README.md so users can set up the database. For D1, users will run `wrangler d1 execute` with your schema. Use Cloudflare secrets via `wrangler secret put SECRET_NAME`. Document required secrets in your README.md. Never commit actual secret values. ## Next Steps [#next-steps] * Review the [Code Standards](/code-standards) for implementation details * Check the [Contributing Guide](/contributing) for PR guidelines * Browse [existing experiments](https://github.com/shrinathsnayak/cloudflare-experiments/tree/main/apps/experiments) for inspiration * Open an [experiment idea issue](https://github.com/shrinathsnayak/cloudflare-experiments/issues/new) to get started Thank you for contributing to Cloudflare Experiments! # Code and Structure Standards (/docs/code-standards) Contributions should follow the project's coding and structure rules to keep the repository consistent and maintainable. ## Experiment Structure [#experiment-structure] Every experiment lives under `apps/experiments//` with this standardized layout: Directory purposes: ### Directory Breakdown [#directory-breakdown] The main entry point for the Worker. This file: * Creates a new Hono app instance * Mounts route handlers * Registers global error handling * Exports `{ fetch: app.fetch }` **Example:** ```typescript import { Hono } from "hono"; import type { Env } from "./types/env"; import checkRoutes from "./routes/check"; const app = new Hono<{ Bindings: Env }>(); app.route("/", checkRoutes); app.get("/", (c) => { return c.json({ name: "is-it-down", description: "Check if a website is reachable from Cloudflare's edge", usage: "GET /check?url=https://www.cloudflare.com", }); }); app.onError((err, c) => { return c.json({ error: err.message, code: "INTERNAL_ERROR" }, 500); }); export default { fetch: app.fetch, }; ``` Contains route handlers only. Each file exports a Hono app with one or more related routes. **Example from `src/routes/check.ts`:** ```typescript import { Hono } from "hono"; import type { Env } from "../types/env"; import { validateUrl } from "../lib/url"; import { fetchWithTiming } from "../lib/fetch"; import { jsonError, jsonSuccess } from "../utils/response"; const app = new Hono<{ Bindings: Env }>(); app.get("/check", async (c) => { const urlParam = c.req.query("url"); const url = validateUrl(urlParam); if (!url) { return jsonError(c, "Missing or invalid query parameter: url", "INVALID_URL"); } const result = await fetchWithTiming(url); return jsonSuccess(c, result); }); export default app; ``` Domain logic and business functionality. This includes: * URL validation * Fetch operations * Data parsing * External API interactions **Example from `src/lib/url.ts`:** ```typescript import { ALLOWED_SCHEMES } from "../constants/defaults"; /** * Validates and normalizes a URL for safe fetching. * Returns the URL string or null if invalid/not allowed. */ export function validateUrl(input: string | undefined): string | null { if (!input || typeof input !== "string") return null; const trimmed = input.trim(); if (!trimmed) return null; try { const url = new URL(trimmed); if (!ALLOWED_SCHEMES.includes(url.protocol as (typeof ALLOWED_SCHEMES)[number])) { return null; } return url.href; } catch { return null; } } ``` Shared helper functions used across the experiment. **Example from `src/utils/response.ts`:** ```typescript import type { Context } from "hono"; export function jsonError( c: Context, message: string, code: string, status: 400 | 404 | 502 = 400 ) { return c.json({ error: message, code }, { status }); } export function jsonSuccess(c: Context, data: T, status: 200 = 200) { return c.json(data, { status }); } ``` Optional directory for configuration values and literals. **Example from `src/constants/defaults.ts`:** ```typescript /** Max time (ms) to wait for the target URL to respond. */ export const FETCH_TIMEOUT_MS = 15_000; /** Allowed URL schemes for security. */ export const ALLOWED_SCHEMES = ["http:", "https:"] as const; ``` All TypeScript type definitions. Worker environment bindings go in `env.d.ts`. **Example from `src/types/env.d.ts`:** ```typescript /// export interface Env { DB: D1Database; LINKS_CACHE: KVNamespace; } ``` ## Core Principles [#core-principles] **No shared code between experiments**: Each experiment is standalone with its own `package.json` and dependencies. Do not import from other experiments or the repo root. **Single responsibility**: One experiment = one Cloudflare capability (e.g. Workers AI, Browser Rendering, D1). **Edge-first, under \~60 seconds**: Prefer stateless, fast request paths that complete quickly. ## TypeScript and API Style [#typescript-and-api-style] ### Strict TypeScript [#strict-typescript] * Use **strict** TypeScript; avoid `any` * Type Worker env in `src/types/env.d.ts` and use `Hono<{ Bindings: Env }>` * All types should be explicitly defined ### Error Handling [#error-handling] Use a shared helper for client errors: ```typescript jsonError(c, message, code, status); ``` **Status code guidelines:** * `400` for bad request (invalid input) * `404` for not found * `502` for server/upstream errors (don't expose internals) **Example:** ```typescript if (!url) { return jsonError(c, "Missing or invalid query parameter: url", "INVALID_URL"); } ``` ### Success Responses [#success-responses] Use `jsonSuccess(c, data)` for JSON success responses: ```typescript return jsonSuccess(c, { status: "reachable", responseTime: 87 }); ``` ### Global Error Handler [#global-error-handler] In `src/index.ts`, register `app.onError(...)` so uncaught errors return consistent JSON: ```typescript app.onError((err, c) => { return c.json({ error: err.message, code: "INTERNAL_ERROR" }, 500); }); ``` ### URL Parameter Validation [#url-parameter-validation] For any endpoint that takes a `url` query param: * Validate with a shared `validateUrl(input)` in `src/lib/url.ts` * Allow only `http://` and `https://` * Return `jsonError` with a clear message and code (e.g. `INVALID_URL`) when invalid **Example:** ```typescript const url = validateUrl(urlParam); if (!url) { return jsonError(c, "Missing or invalid query parameter: url", "INVALID_URL"); } ``` ### Naming Conventions [#naming-conventions] * **Error codes**: Descriptive uppercase with underscores (e.g. `INVALID_URL`, `FETCH_ERROR`, `INTERNAL_ERROR`) * **Routes**: Files in `src/routes/` with kebab-case filenames matching the path (e.g. `check.ts` for `/check`) * **Functions**: camelCase for functions and variables * **Types**: PascalCase for interfaces and types ## Real-World Examples [#real-world-examples] ### Error Handling Pattern [#error-handling-pattern] From `src/lib/fetch.ts` in the `is-it-down` experiment: ```typescript export async function fetchWithTiming(url: string): Promise { const start = Date.now(); const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS); try { const res = await fetch(url, { method: "GET", signal: controller.signal, headers: { "User-Agent": "Cloudflare-Experiments-IsItDown/1.0" }, }); const responseTimeMs = Date.now() - start; clearTimeout(timeoutId); return { ok: res.ok, statusCode: res.status, responseTimeMs, }; } catch (e) { clearTimeout(timeoutId); const responseTimeMs = Date.now() - start; const message = e instanceof Error ? e.message : "Unknown error"; return { ok: false, statusCode: 0, responseTimeMs, error: message, }; } } ``` ### Database Operations with Error Handling [#database-operations-with-error-handling] From `src/routes/shorten.ts` in the `link-shortener` experiment: ```typescript let code = generateCode(); const maxAttempts = 5; for (let attempt = 0; attempt < maxAttempts; attempt++) { try { await db.prepare("INSERT INTO links (code, url) VALUES (?, ?)").bind(code, url).run(); await setCachedLink(c.env, code, url); return c.json({ code, url } satisfies ShortenResponse, 201); } catch (e) { const err = e as { message?: string }; if (err.message?.includes("UNIQUE constraint")) { code = generateCode(); continue; } throw e; } } return jsonError(c, "Could not generate unique code", "INTERNAL_ERROR", 502); ``` ## Configuration Files [#configuration-files] ### wrangler.toml/wrangler.json [#wranglertomlwranglerjson] Declare all Cloudflare bindings (D1, KV, R2, AI, etc.) in this file. These bindings must also be typed in `src/types/env.d.ts`. ### package.json [#packagejson] Each experiment has its own dependencies. Common dependencies: * `hono` - Web framework * `@cloudflare/workers-types` - TypeScript types for Workers ### tsconfig.json [#tsconfigjson] Enable strict mode and configure paths appropriately for the experiment. ## Quality Checklist [#quality-checklist] Before submitting a PR, ensure: * [ ] TypeScript strict mode is enabled with no `any` types * [ ] All errors use `jsonError` with descriptive codes * [ ] Global error handler is registered in `index.ts` * [ ] URL parameters are validated with `validateUrl` * [ ] Environment bindings are declared in both `wrangler.toml` and `src/types/env.d.ts` * [ ] Routes are in `src/routes/`, logic in `src/lib/`, helpers in `src/utils/` * [ ] The experiment can be deployed with `npx wrangler deploy` * [ ] README.md documents the API and usage ## Next Steps [#next-steps] * Learn [how to add new experiments](/adding-experiments) * Review the [Contributing Guide](/contributing) * Explore existing experiments in the [repository](https://github.com/shrinathsnayak/cloudflare-experiments/tree/main/apps/experiments) # Contributing Guide (/docs/contributing) Thank you for your interest in contributing to Cloudflare Experiments. This guide explains how to propose changes, add new experiments, and follow the project's standards. ## How to Contribute [#how-to-contribute] There are several ways to contribute to this project: * **Bug fixes and improvements**: Open an issue first (optional but helpful), then submit a pull request * **New experiments**: Open an issue with the Experiment idea template to discuss before implementing * **Documentation**: Fixes and improvements to README or CONTRIBUTING are always welcome via PR By participating in this project, you agree to abide by our [Code of Conduct](https://github.com/shrinathsnayak/cloudflare-experiments/blob/main/CODE_OF_CONDUCT.md). ## Development Setup [#development-setup] Each experiment is fully independent with its own dependencies. Follow these steps to set up your development environment: ### Fork and clone the repository [#fork-and-clone-the-repository] Fork the [cloudflare-experiments](https://github.com/shrinathsnayak/cloudflare-experiments) repository and clone it to your local machine. ### Install dependencies [#install-dependencies] Navigate to the specific experiment you want to work on: ```bash cd apps/experiments/ npm install ``` ### Run locally [#run-locally] Start the development server: ```bash npm run dev # or: npx wrangler dev ``` ### Lint and type-check [#lint-and-type-check] If the experiment has these scripts configured: ```bash npm run lint npm run build ``` ## Pull Request Process [#pull-request-process] Follow these steps when submitting a pull request: ### Create a branch [#create-a-branch] Create a branch from `main` with a descriptive name: * `fix/whereami-typo` for bug fixes * `feat/new-experiment-name` for new experiments ### Make focused changes [#make-focused-changes] Make your changes in **one experiment only** per PR when possible. Keep PRs focused and easy to review. ### Test deployment [#test-deployment] Ensure the experiment still runs and deploys: ```bash npx wrangler deploy ``` Run this from the experiment's directory. ### Update documentation [#update-documentation] Update the experiment's **README.md** if you changed behavior or added options. ### Add new experiments to the table [#add-new-experiments-to-the-table] If adding a **new experiment**, add a row to the experiments table in the **root README.md** and ensure the experiment follows the [repository structure](/code-standards#experiment-structure). ### Open a pull request [#open-a-pull-request] Open a pull request against `main` with a clear title and description. Fill in the PR template. ### Address feedback [#address-feedback] Address review feedback. Once approved, a maintainer will merge. ## Community Guidelines [#community-guidelines] ### Our Pledge [#our-pledge] We pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation. ### Our Standards [#our-standards] Examples of behavior that contributes to a positive environment: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior: * The use of sexualized language or imagery, and sexual attention or advances of any kind * Trolling, insulting or derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ### Enforcement [#enforcement] Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the project maintainers. All complaints will be reviewed and investigated promptly and fairly. ## Getting Help [#getting-help] Open a [Discussion](https://github.com/shrinathsnayak/cloudflare-experiments/discussions) for questions or ideas about the project. Use [Issues](https://github.com/shrinathsnayak/cloudflare-experiments/issues) for bugs or feature requests. Provide as much detail as possible. Yes! Documentation improvements, bug reports, feature suggestions, and helping others in Discussions are all valuable contributions. Don't be discouraged! We appreciate all contributions. If a PR isn't accepted, maintainers will explain why and may suggest alternatives. ## Next Steps [#next-steps] * Review the [Code Standards](/code-standards) to understand the project structure * Learn [how to add new experiments](/adding-experiments) * Check out existing experiments in the [repository](https://github.com/shrinathsnayak/cloudflare-experiments/tree/main/apps/experiments) Thanks for contributing to Cloudflare Experiments! # Introduction (/docs) A curated collection of experiments demonstrating what developers can build using the **Cloudflare platform**. The goal of this repository is to showcase **real-world developer tools and utilities** that run entirely on the **Cloudflare edge**, often without requiring any backend servers or persistent storage. ## What You'll Find Here [#what-youll-find-here] Each experiment is: * **Small and focused** - Single responsibility, demonstrating one Cloudflare capability * **Independently deployable** - Every experiment has its own deploy button * **Easy to understand** - Clear code structure with TypeScript * **Fast to run** - Designed to execute in under 60 seconds * **Click-to-deploy** - One-click deployment to Cloudflare Workers Deploy your first experiment in minutes Learn why this project exists Explore 60+ edge computing experiments Add your own experiments ## Platform Capabilities [#platform-capabilities] Every experiment demonstrates **practical capabilities of the Cloudflare platform**: Summarization, translation, sentiment, embeddings, image generation, and AI Gateway Screenshots, PDF export, readability extraction, and browser automation DNS lookup, propagation checks, uptime monitoring, CORS testing, and geolocation Web scraping, social preview inspection, parsing, and HTML transformation Edge databases, mock APIs, key-value storage, and semantic search Object storage, change tracking, Durable Objects, Queues, and Cron Triggers ## Experiment Categories [#experiment-categories] * **AI Website Summary** - Summarize any webpage using Workers AI * **AI Website Tag Generator** - Generate topic tags for any website * **GitHub Repo Explainer** - AI explanation of GitHub repositories * **AI Bot Visibility** - Check if URLs are configured for AI crawlers * **Cloud AI Proxy** - Call Workers AI with any model and prompt from one endpoint * **Text Translator** - Translate text with Workers AI at the edge * **Sentiment Analyzer** - Analyze text sentiment with Workers AI * **Text Similarity** - Compare text similarity with Workers AI embeddings * **AI Image Generator** - Generate images from text prompts with Workers AI * **Speech to Text Transcriber** - Transcribe uploaded audio with Workers AI Whisper * **RAG Mini Search** - Grounded Q\&A with Vectorize retrieval and Workers AI * **AI Gateway Dashboard** - Workers AI through AI Gateway with cache and latency metadata * **Website Metadata Extractor** - Extract title, description, Open Graph data, and canonical URL * **Website to API** - Turn webpages into structured JSON - **Website to llms.txt** - Convert pages for LLM consumption - **DevTools Inspector** - DevTools-style inspection of websites - **Dependency Analyzer** - Analyze external resources loaded by a page - **HTML Rewriter** - Extract HTML stats and transform pages with HTMLRewriter - **Social Preview Inspector** - Preview Twitter/X, Open Graph, and Google snippets side by side - **Readability Extractor** - Extract clean article content with readability-style heuristics * **Screenshot API** - Capture screenshots from the edge - **PDF API** - Generate PDF documents from any webpage - **Page Metrics** - Collect Puppeteer page load metrics - **Rendered Text** - Extract JavaScript-rendered visible text - **Browser Links** - Extract links from JavaScript-rendered pages * **Is It Down** - Check if websites are reachable from the edge - **URL DNS Lookup** - Get DNS records for any hostname - **DNS Propagation Checker** - Compare answers from Cloudflare, Google, and Quad9 resolvers - **Edge Redirect Simulator** - Trace redirect chains and status codes - **Where Am I** - Request metadata and geolocation from the edge - **Response Headers** - Inspect HTTP response headers for any URL - **SSL Certificate Inspector** - Inspect TLS certificate metadata for a domain - **Multi-PoP Latency Map** - Fetch latency plus serving colo per request - **Website Change Tracker** - Scheduled snapshots with R2 storage and D1 diff history - **Uptime Monitor Alerts** - Persistent URL monitors with D1 history and email alerts - **CORS Preflight Tester** - Simulate browser CORS preflight and analyze response headers * **Edge Cache** - Fetch URLs with the Workers Cache API - **Crypto Hash** - Compute SHA digests with the Web Crypto API - **WebSocket Echo** - WebSocket echo server on Workers - **Image Resizer** - Resize remote images with Cloudflare Image Resizing - **Turnstile Verify** - Verify Cloudflare Turnstile tokens via siteverify - **JWT Inspector** - Decode, verify, and issue JWTs for experimentation - **Rate Limiter Demo** - Native Workers Rate Limiting binding with 429 responses - **Webhook Signature Verifier** - Verify HMAC-SHA256 webhook signatures with timing-safe compare * **R2 Storage** - R2 object storage with list/get/put/delete - **Link Shortener** - URL shortener using D1 and KV - **D1 SQL Playground** - Read-only SQL playground over a seeded D1 database - **KV Notes** - Simple note storage with Workers KV - **Vectorize Search** - Semantic search with Workers AI embeddings and Vectorize - **Presigned R2 Upload** - Presigned PUT URLs for direct browser-to-R2 uploads - **API Mock Server** - Define mock HTTP endpoints in KV and serve them on demand * **Durable Counter** - Globally consistent counter with Durable Objects * **Cron Heartbeat** - Scheduled tasks with Cron Triggers and KV metadata * **Task Queue** - Enqueue tasks over HTTP and process them asynchronously * **Analytics Engine** - Write custom events to Workers Analytics Engine * **Workflows Pipeline Demo** - Durable fetch → AI summarize → R2 pipeline with Workflows * **Live Cursor Tracker** - Real-time shared cursors over WebSocket via Durable Objects * **Queue Job Visualizer** - Queues producer/consumer with KV job status and simulated retries * **DO Alarm Scheduler** - One-off reminders with the Durable Object Alarm API * **Webhook Relay Inspector** - Capture inbound webhooks in a Durable Object session for debugging ## Why This Project Exists [#why-this-project-exists] Most Cloudflare tutorials show very simple examples (Hello World, basic KV counters, simple fetch). This repository focuses instead on **real tools developers would actually want to use**. Each experiment is production-ready and demonstrates a complete use case that you can deploy, customize, and learn from. Learn about the design principles and goals behind this project ## Key Features [#key-features] Every experiment includes a Cloudflare Deploy Button - deploy a single experiment without touching the others Many experiments use edge compute, fetch, and HTML parsing - no persistent storage required Each experiment demonstrates one specific Cloudflare capability Run at Cloudflare's edge in 300+ cities worldwide ## Getting Started [#getting-started] Ready to explore? Start with the [Quick Start guide](/quickstart) to deploy your first experiment, or browse the experiments by category using the sidebar. Use search with tags like `ai`, `browser`, `network`, or binding names like `d1` and `r2` to filter experiments quickly. All experiments are open source and licensed under MIT. Fork, customize, and deploy them for your own use cases. # Philosophy (/docs/philosophy) Cloudflare Experiments is built on the principle that **developers learn best by studying reference implementations** for Cloudflare products - not toy examples or generic utilities. ## The Problem [#the-problem] Most Cloudflare tutorials show very simple examples: * "Hello World" workers * Basic KV counters * Simple fetch proxies While these are great for getting started, they don't showcase the **real power** of the Cloudflare platform. Developers are left wondering: "Okay, I can return 'Hello World' from the edge... but what can I actually *build* with this?" ## The Solution [#the-solution] Cloudflare Experiments bridges this gap by providing a curated collection of **reference implementations** - small, deployable examples that show how to wire up specific Cloudflare products and services. Every experiment in this repository is: ### Small and Focused [#small-and-focused] Each experiment demonstrates **one specific capability** of the Cloudflare platform: * **Workers AI** → AI Website Summary, GitHub Repo Explainer * **Browser Rendering** → Screenshot API * **HTMLRewriter** → Website to API, Dependency Analyzer * **Edge Networking** → Is It Down, URL DNS Lookup * **D1 + KV** → Link Shortener * **R2** → R2 Storage * **Durable Objects** → Durable Counter * **Cron Triggers** → Cron Heartbeat * **Queues** → Task Queue * **Request Metadata** → Where Am I, AI Bot Visibility **Single responsibility principle**: One experiment, one capability. This makes the code easy to understand and the concept easy to grasp. ### Independently Deployable [#independently-deployable] Every experiment is **completely independent**: ``` experiments/ ├── ai-website-summary/ # Independent │ ├── package.json │ ├── wrangler.json │ ├── src/ │ └── README.md ├── screenshot-api/ # Independent │ ├── package.json │ ├── wrangler.json │ ├── src/ │ └── README.md └── is-it-down/ # Independent ├── package.json ├── wrangler.json ├── src/ └── README.md ``` **No shared code between experiments.** Each has its own: * Dependencies (`package.json`) * Configuration (`wrangler.json`) * Types and utilities * Deploy button You can: * Clone just one experiment * Deploy just one experiment * Fork just one experiment * Modify one without touching others This is intentional. While shared code might reduce duplication, it creates coupling that makes experiments harder to understand and reuse independently. ### Easy to Understand [#easy-to-understand] Every experiment follows the **same structure**: ```typescript // src/index.ts - Always the entry point import { Hono } from "hono"; import type { Env } from "./types/env"; import whereamiRoutes from "./routes/whereami"; const app = new Hono<{ Bindings: Env }>(); app.route("/", whereamiRoutes); // Always include a GET / for experiment info app.get("/", (c) => { return c.json({ name: "whereami", description: "Request metadata from Cloudflare's edge (request.cf)", usage: "GET /whereami", }); }); // Always include global error handling app.onError((err, c) => { return c.json({ error: err.message, code: "INTERNAL_ERROR" }, 500); }); export default { fetch: app.fetch, }; ``` **Consistent patterns**: * Use [Hono](https://hono.dev/) for routing (fast, type-safe, Workers-optimized) * Use TypeScript with strict typing * Use shared error/success helpers (`jsonError`, `jsonSuccess`) * Validate inputs (especially URLs) with clear error codes * Include comprehensive error handling ### Designed to Run Fast [#designed-to-run-fast] **Goal: Under 60 seconds** from clicking "Deploy" to having a working API. This means: * **Stateless first**: Most experiments use edge compute and fetch-no persistent storage required * **No complex setup**: Works with default Cloudflare settings where possible * **Minimal dependencies**: Each experiment installs only what it needs * **Edge-optimized**: Run close to users with sub-100ms response times **Example**: The "Is It Down" experiment: 1. Click Deploy button 2. Authenticate with Cloudflare 3. Worker is live in \~30 seconds 4. Test: `curl "https://your-worker.workers.dev/check?url=https://example.com"` No database setup. No API keys. No configuration files. ### Click-to-Deploy Ready [#click-to-deploy-ready] Every experiment includes a **Deploy to Cloudflare Workers** button: ```markdown [![Deploy to Cloudflare Workers](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/shrinathsnayak/cloudflare-experiments/tree/main/apps/experiments/is-it-down) ``` This lowers the barrier to experimentation. You can: * Try before you clone * Deploy to production in one click * Fork and modify the deploy URL to use your own repo ## Design Principles [#design-principles] ### 1. Product Reference, Not Generic Utilities [#1-product-reference-not-generic-utilities] Every experiment maps to a **specific Cloudflare product or binding**: Reference pattern: globally consistent state with Durable Objects Reference pattern: async processing with Queues producer/consumer Reference pattern: D1 primary storage with KV read cache Reference pattern: headless browser automation with Browser Rendering These are **starting points you can copy** when building on Cloudflare - not standalone utilities that happen to run on Workers. ### 2. Edge-First Architecture [#2-edge-first-architecture] Cloudflare's edge network spans 300+ cities. Experiments demonstrate **how to leverage this**: **Example: Is It Down** Instead of checking from a single server location: ```javascript // Traditional approach (single server) const response = await fetch(url); return response.ok ? "up" : "down"; ``` Check from the edge closest to the user: ```javascript // Edge approach (Cloudflare) const cf = c.req.raw.cf; const result = await fetchWithTiming(url); return { status: result.ok ? "reachable" : "unreachable", responseTime: result.responseTimeMs, colo: cf?.colo, // Which edge location served this }; ``` The user in London gets results from LHR (London Heathrow), the user in Tokyo gets results from NRT (Narita). **Global performance by default.** ### 3. Platform Capabilities Over Abstractions [#3-platform-capabilities-over-abstractions] Experiments showcase **native Cloudflare features**: **Workers AI** (not external AI APIs): ```typescript // experiments/ai-website-summary/src/lib/ai.ts const ai = c.env.AI; const response = await ai.run("@cf/meta/llama-2-7b-chat-int8", { messages: [ { role: "user", content: `Summarize this webpage: ${text}`, }, ], }); ``` **Browser Rendering** (not external screenshot services): ```typescript // experiments/screenshot-api/src/lib/screenshot.ts const browser = await puppeteer.launch(c.env.BROWSER); const page = await browser.newPage(); await page.goto(url); const screenshot = await page.screenshot(); ``` **HTMLRewriter** (not external parsing services): ```typescript // experiments/website-to-api/src/lib/parser.ts new HTMLRewriter() .on("h1", { element(element) { headings.push(element.getAttribute("textContent")); }, }) .transform(response); ``` Every experiment teaches you **what Cloudflare can do natively** without external dependencies. ### 4. Consistent Code Standards [#4-consistent-code-standards] All experiments follow the same conventions: **Error Handling**: ```typescript // src/utils/response.ts (duplicated in each experiment) export function jsonError(c: Context, message: string, code: string, status = 400) { return c.json({ error: message, code }, status); } export function jsonSuccess(c: Context, data: unknown) { return c.json(data); } ``` **URL Validation**: ```typescript // src/lib/url.ts (duplicated in each experiment) export function validateUrl(input: string | undefined): string | null { if (!input) return null; try { const url = new URL(input); if (url.protocol !== "http:" && url.protocol !== "https:") { return null; } return url.href; } catch { return null; } } ``` **Error Codes**: * `INVALID_URL` - Bad or missing URL parameter * `FETCH_ERROR` - Failed to fetch external resource * `INTERNAL_ERROR` - Uncaught exception * `NOT_FOUND` - Resource doesn't exist Consistent patterns make the codebase **predictable and easy to navigate**. ### 5. TypeScript All the Way [#5-typescript-all-the-way] Every experiment uses **strict TypeScript**: ```typescript // src/types/env.d.ts export interface Env { AI?: Ai; // Workers AI binding BROWSER?: Fetcher; // Browser Rendering binding DB?: D1Database; // D1 binding LINKS_CACHE?: KVNamespace; // KV binding } ``` ```typescript // src/types/check.ts export type CheckResponse = | { status: "reachable"; responseTime: number; statusCode: number; colo?: string; } | { status: "unreachable"; responseTime: number; statusCode?: number; colo?: string; error?: string; }; ``` Full type safety from request to response. ## What This Means for You [#what-this-means-for-you] ### As a Learner [#as-a-learner] You can: * **Learn by example**: See real implementations of Cloudflare features * **Copy and modify**: Each experiment is a starting point for your own projects * **Understand the platform**: See what's possible with Workers, AI, D1, KV, etc. ### As a Builder [#as-a-builder] You can: * **Deploy immediately**: Click-to-deploy to production * **Fork and customize**: Each experiment is MIT licensed * **Build on top**: Use experiments as building blocks for larger applications ### As a Contributor [#as-a-contributor] You can: * **Add new experiments**: Showcase other Cloudflare capabilities (Vectorize, Images, Email Workers) * **Improve existing ones**: Better error handling, new features, performance optimizations * **Fix bugs**: Help make the examples more robust See [CONTRIBUTING.md](https://github.com/shrinathsnayak/cloudflare-experiments/blob/main/CONTRIBUTING.md) for guidelines. ## Future Directions [#future-directions] The collection will continue to grow with experiments demonstrating: * **Vectorize** - Vector search at the edge * **Images API** - On-the-fly image transformation * **Email Workers** - Process incoming email * **Hyperdrive** - Accelerate database queries * **Workers Analytics Engine** - Event logging and analytics Each new experiment will follow the same principles: **real tools**, **independently deployable**, **easy to understand**, and **edge-first**. ## Why It Matters [#why-it-matters] Cloudflare's edge platform is **incredibly powerful**, but the learning curve can be steep. Cloudflare Experiments makes it tangible: * See **concrete examples** of what you can build * Deploy **real tools** in under 60 seconds * Learn **platform capabilities** through working code * Build **production patterns** from day one The goal is simple: **help developers discover what's possible** on the Cloudflare edge by showing them reference implementations they can deploy, read, and adapt. The best way to learn is to deploy an experiment, look at the code, and modify it. Start with something simple like [Where Am I](https://github.com/shrinathsnayak/cloudflare-experiments/tree/main/apps/experiments/whereami), then move to more complex experiments like [Link Shortener](https://github.com/shrinathsnayak/cloudflare-experiments/tree/main/apps/experiments/link-shortener). ## Get Involved [#get-involved] Star the repo and explore the code Learn how to contribute your own experiments Deploy your first experiment now Ask questions and share ideas # Quickstart (/docs/quickstart) Get started with Cloudflare Experiments by deploying a simple worker to the edge. Each experiment is designed to run in under 60 seconds and demonstrates real-world capabilities of the Cloudflare platform. ## Deploy an Experiment [#deploy-an-experiment] The fastest way to get started is using the one-click deploy button. We'll use the **Where Am I** experiment as an example. ### Choose an experiment [#choose-an-experiment] Browse the [available experiments](/experiments) and pick one to deploy. For this guide, we'll deploy [Where Am I](/experiments/whereami), which returns geolocation data from Cloudflare's edge. ### Click the deploy button [#click-the-deploy-button] Click the Deploy to Cloudflare Workers button from any experiment page or the main repository. [![Deploy to Cloudflare Workers](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/shrinathsnayak/cloudflare-experiments/tree/main/apps/experiments/whereami) ### Authenticate with Cloudflare [#authenticate-with-cloudflare] If you haven't already, you'll be prompted to sign in to your Cloudflare account. The deployment process will automatically: * Create a new Worker * Configure the necessary bindings * Deploy to Cloudflare's global edge network ### Access your deployed worker [#access-your-deployed-worker] Once deployed, you'll receive a `*.workers.dev` URL where your experiment is live. Deploy from [shrinathsnayak/cloudflare-experiments](https://github.com/shrinathsnayak/cloudflare-experiments). To deploy from your own fork, change the owner in the deploy URL. ## Test Your Deployment [#test-your-deployment] Once deployed, test your worker using curl or your browser. ### Where Am I Example [#where-am-i-example] ```bash curl https://your-worker.workers.dev/whereami ``` **Response:** ```json { "country": "US", "city": "San Francisco", "region": "California", "timezone": "America/Los_Angeles", "colo": "SFO", "asn": 13335 } ``` ### Is It Down Example [#is-it-down-example] Check if a website is reachable from Cloudflare's edge: ```bash curl "https://your-worker.workers.dev/check?url=https://www.cloudflare.com" ``` **Response:** ```json { "status": "reachable", "responseTime": 87, "statusCode": 200, "colo": "LHR" } ``` The `colo` field shows the edge data center (IATA code) that served your request. Cloudflare has over 300 locations worldwide. ## Local Development [#local-development] To modify an experiment or develop locally, clone the repository and run the worker on your machine. ### Install Dependencies [#install-dependencies] ```bash cd apps/experiments/whereami npm install ``` ```bash cd apps/experiments/whereami pnpm install ``` ```bash cd apps/experiments/whereami yarn install ``` ### Start Development Server [#start-development-server] ```bash npm run dev ``` ```bash pnpm dev ``` ```bash yarn dev ``` The worker will be available at `http://localhost:8787`. ### Test Locally [#test-locally] ```bash curl http://localhost:8787/whereami ``` When running locally, the `cf` object may be empty or minimal. Full geolocation data appears when deployed to Cloudflare's edge network. ## What You Can Build [#what-you-can-build] Each experiment in this collection demonstrates **practical capabilities** of the Cloudflare platform: * **Workers** - Edge compute without backend servers * **Workers AI** - Run AI models at the edge * **Browser Rendering** - Capture screenshots and manipulate web pages * **HTMLRewriter** - Transform HTML on the fly * **Edge Networking** - Global request/response handling * **D1** - Serverless SQL database * **R2** - Object storage All experiments are **stateless first** and use edge compute, fetch, and HTML parsing without requiring persistent storage. Learn more about the design [philosophy](/philosophy). ## Next Steps [#next-steps] Browse all available experiments Learn the design principles Add your own experiment View source code # AI Bot Visibility Checker (/docs/experiments/ai-bot-visibility) This is an experimental Worker. Use it as a starting point for your own projects. The AI Bot Visibility Checker analyzes a webpage's `robots.txt` file and page-level directives to determine whether known AI crawlers (like GPTBot, ClaudeBot, PerplexityBot) are allowed or blocked. It provides per-crawler status and a summary by platform. **Disclaimer**: This tool reports **configuration only**. It does not verify whether a URL is actually indexed or used by any AI product, as there are no public APIs for that information. ## Features [#features] * Check 14+ known AI crawler bots in one request * Parse `robots.txt` with user-agent specific rules * Detect page-level blocks (meta robots, X-Robots-Tag headers) * Support for generic directives (`noindex`, `noai`, `noimageai`) * Stateless edge execution (no bindings required) * Sub-second response times ## API Reference [#api-reference] ### GET /check [#get-check] Check AI crawler visibility configuration for a URL. #### Response [#response] **`url`** `string` The URL that was analyzed. **`disclaimer`** `string` Reminder that this reports configuration only, not actual indexing status. **`crawlers`** `array` Array of crawler status objects. **`id`** `string` The crawler's User-Agent token (e.g., "GPTBot", "ClaudeBot"). **`platform`** `string` Human-readable platform name (e.g., "ChatGPT", "Claude"). **`status`** `string` One of: - `"allowed"` - robots.txt and page directives allow this crawler - `"blocked"` - robots.txt or page-level directive blocks this crawler - `"not_specified"` - no rule applies for this crawler **`summary`** `object` Aggregated results by status. **`allowed`** `string[]` Array of platform names that are allowed. **`blocked`** `string[]` Array of platform names that are blocked. **`notSpecified`** `string[]` Array of platform names with no specific rules. #### Example Request [#example-request] ```bash curl "https://your-worker.workers.dev/check?url=https://www.cloudflare.com" ``` #### Example Response [#example-response] ```json { "url": "https://www.cloudflare.com", "disclaimer": "Configuration only; we cannot verify actual index inclusion in any AI product.", "crawlers": [ { "id": "GPTBot", "platform": "ChatGPT", "status": "allowed" }, { "id": "ChatGPT-User", "platform": "ChatGPT", "status": "allowed" }, { "id": "ClaudeBot", "platform": "Claude", "status": "not_specified" }, { "id": "PerplexityBot", "platform": "Perplexity", "status": "blocked" }, { "id": "Google-Extended", "platform": "Google (Gemini/Bard)", "status": "not_specified" } // ... 9 more crawlers ], "summary": { "allowed": ["ChatGPT"], "blocked": ["Perplexity"], "notSpecified": [ "Claude", "ChatGPT Search", "Google (Gemini/Bard)", "Google Vertex AI", "Common Crawl", "ByteDance", "Meta AI", "Meta", "Apple", "Amazon", "DuckDuckGo" ] } } ``` #### Error Responses [#error-responses] **`error`** `string` Human-readable error message. **`code`** `string` Machine-readable error code. **400 Bad Request** - Missing or invalid URL: ```json { "error": "Missing or invalid query parameter: url", "code": "INVALID_URL" } ``` **502 Bad Gateway** - Failed to fetch the URL: ```json { "error": "Failed to fetch URL", "code": "FETCH_ERROR" } ``` ## Monitored AI Crawlers [#monitored-ai-crawlers] The worker checks the following AI crawlers: | Crawler ID | Platform | Description | | ----------------------- | -------------------- | --------------------------- | | `GPTBot` | ChatGPT | OpenAI's web crawler | | `ChatGPT-User` | ChatGPT | ChatGPT user-facing bot | | `OAI-SearchBot` | ChatGPT Search | OpenAI's search crawler | | `ClaudeBot` | Claude | Anthropic's web crawler | | `PerplexityBot` | Perplexity | Perplexity AI's crawler | | `Google-Extended` | Google (Gemini/Bard) | Google's Gemini crawler | | `Google-CloudVertexBot` | Google Vertex AI | Google Vertex AI crawler | | `CCBot` | Common Crawl | Common Crawl corpus builder | | `Bytespider` | ByteDance | ByteDance's crawler | | `Meta-ExternalAgent` | Meta AI | Meta's AI crawler | | `FacebookBot` | Meta | Facebook's web crawler | | `Applebot` | Apple | Apple's web crawler | | `Amazonbot` | Amazon | Amazon's web crawler | | `DuckAssistBot` | DuckDuckGo | DuckDuckGo AI assistant | This list is defined in `src/constants/crawlers.ts` and can be customized for your needs. ## Implementation Details [#implementation-details] ### Detection Logic [#detection-logic] The worker uses a multi-layered approach to determine crawler visibility: ### Fetch robots.txt and page content [#fetch-robotstxt-and-page-content] Both requests are made in parallel: ```typescript const [pageResult, robotsBody] = await Promise.all([ fetchPage(url), fetchRobotsTxt(origin), ]); ``` ### Parse robots.txt rules [#parse-robotstxt-rules] Extract per-user-agent Allow/Disallow directives: ```typescript const rules = robotsBody ? parseRobotsTxt(robotsBody) : new Map(); ``` The parser handles: * User-agent specific rules * Wildcard user-agents (`*`) * Path-specific allow/disallow patterns ### Extract page-level signals [#extract-page-level-signals] Check HTML meta tags and HTTP headers: ```typescript // Detects: // // // X-Robots-Tag: noindex, nofollow const signals = getPageRobotSignals(html, headers); ``` ### Compute per-crawler status [#compute-per-crawler-status] Apply precedence rules for each crawler: ```typescript // 1. Page-level block -> blocked // 2. robots.txt disallow -> blocked // 3. robots.txt allow -> allowed // 4. No rule -> not_specified const status = computeStatus(rules, signals, crawlerId, path); ``` ### Core Implementation [#core-implementation] Here's the main route handler from `src/routes/check.ts`: ```typescript app.get("/check", async (c) => { const url = validateUrl(c.req.query("url")); if (!url) return jsonError(c, "Missing or invalid query parameter: url", "INVALID_URL"); try { const parsed = new URL(url); const origin = parsed.origin; const path = parsed.pathname || "/"; const [pageResult, robotsBody] = await Promise.all([fetchPage(url), fetchRobotsTxt(origin)]); const rules = robotsBody ? parseRobotsTxt(robotsBody) : new Map(); const response = buildVisibilityResponse(url, path, rules, pageResult.html, pageResult.headers); return jsonSuccess(c, response); } catch (e) { const message = e instanceof Error ? e.message : "Failed to fetch URL"; return jsonError(c, message, "FETCH_ERROR", 502); } }); ``` ### Status Precedence [#status-precedence] The visibility logic follows this precedence (from `src/lib/visibility.ts`): ```typescript function computeStatus( rules: RobotsRules, signals: PageRobotSignals, crawlerId: string, path: string ): CrawlerStatus { // Page-level block takes highest precedence if (isCrawlerBlockedByPage(signals, crawlerId)) return "blocked"; // Then check robots.txt const robotsAllow = isAllowedByRobots(rules, crawlerId, path); if (robotsAllow === false) return "blocked"; if (robotsAllow === true) return "allowed"; // No rule specified return "not_specified"; } ``` ### Page-Level Directives [#page-level-directives] The worker detects these meta tags and headers: ```html X-Robots-Tag: noindex, noai ``` ### robots.txt Parsing [#robotstxt-parsing] Example `robots.txt` rules: ```txt # Block specific crawlers User-agent: GPTBot Disallow: / User-agent: PerplexityBot Disallow: /private/ # Allow all others User-agent: * Allow: / ``` The parser extracts per-user-agent rules and applies path matching with longest-prefix-wins logic. ## Advanced Usage [#advanced-usage] ### Adding Custom Crawlers [#adding-custom-crawlers] Extend the crawler list in `src/constants/crawlers.ts`: ```typescript export const AI_CRAWLERS: Array<{ id: string; platform: string }> = [ { id: "GPTBot", platform: "ChatGPT" }, { id: "ClaudeBot", platform: "Claude" }, // Add your custom crawlers { id: "MyCustomBot", platform: "My AI Platform" }, // ... ]; ``` ### Batch Checking Multiple URLs [#batch-checking-multiple-urls] Create a new endpoint to check multiple URLs: ```typescript app.post("/batch-check", async (c) => { const { urls } = await c.req.json(); const results = await Promise.all(urls.map((url) => checkVisibility(url))); return c.json({ results }); }); ``` ### Adding Site-Wide Analysis [#adding-site-wide-analysis] Fetch and parse the sitemap to check all pages: ```typescript import { parseSitemap } from "./lib/sitemap"; app.get("/site-check", async (c) => { const sitemapUrl = c.req.query("sitemap"); const urls = await parseSitemap(sitemapUrl); // Check each URL... }); ``` ## FAQ [#faq] If a site doesn't explicitly allow or block a crawler in robots.txt or meta tags, the status is `not_specified`. This means the crawler could access the page by default, but there's no explicit rule. No. `allowed` means the page configuration doesn't block the crawler. Actual indexing depends on many factors: crawler behavior, content quality, update frequency, and internal platform decisions. Not with the default implementation. You'd need to add authentication headers to the fetch requests and handle cookies/sessions. The parser handles standard robots.txt directives (User-agent, Allow, Disallow) with path prefix matching. It doesn't support regex patterns or advanced extensions. This tool reports configuration only. If a crawler doesn't respect robots.txt (which is voluntary), you'd need to block it at the network level (firewall, CDN rules, etc.). ## Use Cases [#use-cases] * **SEO Tools** - Add AI crawler visibility checks to SEO audit dashboards * **CMS Plugins** - Integrate into WordPress/Drupal to show AI bot status * **Privacy Compliance** - Monitor which AI platforms can access your content * **Analytics Dashboards** - Track AI crawler access policies across sites * **Browser Extensions** - Show AI visibility status for the current page * **Web Scraping Tools** - Check if your scraper is allowed before crawling ## Limitations [#limitations] * **Configuration only**: Does not verify actual indexing by AI platforms (no public APIs exist) * **Static analysis**: Does not execute JavaScript; only analyzes HTML and headers * **No authentication**: Cannot check auth-protected pages * **robots.txt compliance**: Assumes crawlers respect robots.txt (not legally enforced) * **Limited crawlers**: Only checks 14 known AI crawlers (list can be extended) * **No sitemap parsing**: Only checks individual URLs, not entire sitemaps ## Deployment [#deployment] ### Click the deploy button [#click-the-deploy-button] [![Deploy to Cloudflare Workers](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/shrinathsnayak/cloudflare-experiments/tree/main/apps/experiments/ai-bot-visibility) ### Deploy [#deploy] Follow the deployment wizard to deploy the Worker to your Cloudflare account. No additional configuration or bindings required. ### Test your deployment [#test-your-deployment] ```bash curl "https://your-worker.workers.dev/check?url=https://www.cloudflare.com" ``` ## Local Development [#local-development] ```bash cd apps/experiments/ai-bot-visibility npm install npm run dev ``` Test locally: ```bash curl "http://localhost:8787/check?url=https://www.cloudflare.com" ``` ## Configuration [#configuration] No bindings or environment variables are required. The `wrangler.json` is minimal: ```json { "name": "ai-bot-visibility", "main": "src/index.ts", "compatibility_date": "2024-01-01" } ``` ### Dependencies [#dependencies] ```json { "dependencies": { "hono": "^4.6.12" }, "devDependencies": { "@cloudflare/workers-types": "^4.20241127.0", "typescript": "^5.7.2", "wrangler": "^4" } } ``` ## Cloudflare Features Used [#cloudflare-features-used] * **[Workers](https://developers.cloudflare.com/workers/)** - Serverless execution environment * **[Fetch API](https://developers.cloudflare.com/workers/runtime-apis/fetch/)** - HTTP client for fetching pages and robots.txt * **[Edge network](https://developers.cloudflare.com/workers/reference/how-workers-works/)** - Low-latency requests from global edge locations ## Next Steps [#next-steps] Learn about robots.txt syntax and best practices Documentation for meta robots directives Cloudflare's AI crawler blocking feature View the complete source code # AI Gateway Dashboard (/docs/experiments/ai-gateway-dashboard) Call **Workers AI** through **AI Gateway** instead of direct inference. Returns generated text plus gateway metadata (cache status, latency). Optionally compare cached vs fresh requests for the same prompt. ## Features [#features] * POST /generate - text generation via AI Gateway * Returns latency and cache hit/miss metadata * Optional compareCache mode runs cached vs skipCache requests ## API Reference [#api-reference] ### POST /generate [#post-generate] **`prompt`** `string` (required) - Text prompt for the model. **`compareCache`** `boolean` (optional) - Run cached and fresh requests and compare latency. Default model: `@cf/meta/llama-3.1-8b-instruct-fast`. #### Example Request [#example-request] ```bash curl -X POST "https://your-worker.workers.dev/generate" \ -H "Content-Type: application/json" \ -d '{"prompt":"Explain Workers AI in one sentence","compareCache":true}' ``` #### Error Codes [#error-codes] * `400` - `INVALID_BODY`, `MISSING_PROMPT` * `502` - `AI_ERROR` ## Use Cases [#use-cases] * Learn AI Gateway cache behavior vs direct Workers AI calls * Compare latency for repeated prompts in prototyping * Reference pattern for gateway options in production Workers ## Limitations [#limitations] * Requires Workers AI and AI Gateway enabled on your account * Cache metadata depends on gateway configuration * Text generation only; fixed default model ## Deployment [#deployment] ### Click the deploy button [#click-the-deploy-button] [![Deploy to Cloudflare Workers](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/shrinathsnayak/cloudflare-experiments/tree/main/apps/experiments/ai-gateway-dashboard) ### Configure bindings [#configure-bindings] AI binding in `wrangler.json`. Use gateway id `default` or your own gateway ID in code. ### Test your deployment [#test-your-deployment] See the experiment README for curl examples. ## Local Development [#local-development] ```bash cd apps/experiments/ai-gateway-dashboard npm install npm run dev ``` ## Configuration [#configuration] AI binding in `wrangler.json`. Use gateway id `default` or your own gateway ID in code. ## Cloudflare Features Used [#cloudflare-features-used] * **[Workers AI](https://developers.cloudflare.com/workers-ai/)** * **[AI Gateway](https://developers.cloudflare.com/ai-gateway/)** # AI Image Generator (/docs/experiments/ai-image-generator) Generate PNG images from text prompts using [Workers AI](https://developers.cloudflare.com/workers-ai/) and the `@cf/black-forest-labs/flux-1-schnell` model at the edge. ## Features [#features] * Generate PNG images from text prompts at the edge * Workers AI image model (`@cf/black-forest-labs/flux-1-schnell`) * Returns binary image responses directly from the Worker * No external image API keys required ## API Reference [#api-reference] ### GET /generate [#get-generate] Generates a PNG image from the provided text prompt. #### Example Request [#example-request] ```bash curl "https://your-worker.workers.dev/generate?prompt=a%20sunset%20over%20snow-capped%20mountains" \ --output image.png ``` #### Success Response [#success-response] Returns a PNG image with the following header: ```http Content-Type: image/png ``` #### Error Response [#error-response] ```json { "error": "Missing or invalid query parameter: prompt", "code": "INVALID_PROMPT" } ``` #### Error Codes [#error-codes] * `400` - Missing or invalid `prompt` (`INVALID_PROMPT`) * `502` - Workers AI model run failed (`AI_ERROR`) ## Use Cases [#use-cases] * Prototype AI image generation without external API keys * Generate placeholder or marketing visuals from text at the edge * Learn Workers AI image models in a stateless Worker * Build creative tools that return binary image responses directly ## Limitations [#limitations] * Workers AI image generation is subject to [usage limits](https://developers.cloudflare.com/workers-ai/platform/limits/) by plan * Fixed model and parameters; no style, size, or batch controls in this experiment * Returns a single PNG per request; no async jobs or storage * Public endpoint with no authentication or rate limiting ## Deployment [#deployment] ### Click the deploy button [#click-the-deploy-button] [![Deploy to Cloudflare Workers](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/shrinathsnayak/cloudflare-experiments/tree/main/apps/experiments/ai-image-generator) ### Deploy [#deploy] Enable the **Workers AI** binding (`AI`) in your Worker settings. The deploy button configures this automatically via `wrangler.json`. Requires a Cloudflare account with Workers AI enabled. ### Test your deployment [#test-your-deployment] ```bash curl "https://your-worker.workers.dev/generate?prompt=a%20sunset%20over%20snow-capped%20mountains" \ --output image.png ``` ## Local Development [#local-development] ```bash cd apps/experiments/ai-image-generator npm install npm run dev ``` Test locally: ```bash curl "http://localhost:8787/generate?prompt=a%20cyberpunk%20cityscape" \ --output image.png ``` ## Cloudflare Features Used [#cloudflare-features-used] * **[Workers](https://developers.cloudflare.com/workers/)** - Edge compute runtime * **[Workers AI](https://developers.cloudflare.com/workers-ai/)** - `@cf/black-forest-labs/flux-1-schnell` image generation model # AI Website Summary (/docs/experiments/ai-website-summary) This is an experimental Worker. Use it as a starting point for your own projects. The AI Website Summary experiment demonstrates how to fetch any webpage, extract its content, and generate a concise summary using [Workers AI](https://developers.cloudflare.com/workers-ai/). Built with Hono and TypeScript, it runs entirely on Cloudflare's edge network. ## Features [#features] * Fetch and parse any public webpage * Extract title and body text from HTML * Generate 2-4 sentence summaries using Llama 3.1 * Sub-second response times on the edge * No external API keys required ## API Reference [#api-reference] ### GET /summary [#get-summary] Generate an AI summary of any webpage. #### Response [#response] **`title`** `string | null` The extracted page title from the HTML `` tag. **`summary`** `string` A 2-4 sentence AI-generated summary of the page content. #### Example Request [#example-request] ```bash curl "https://your-worker.workers.dev/summary?url=https://www.cloudflare.com" ``` #### Example Response [#example-response] ```json { "title": "Cloudflare - The Web Performance & Security Company", "summary": "Cloudflare provides a content delivery network and DDoS protection services. The platform offers security, performance, and reliability solutions for websites and applications. It includes features like CDN, DNS, and web application firewall to protect and accelerate online properties." } ``` #### Error Responses [#error-responses] **`error`** `string` Human-readable error message. **`code`** `string` Machine-readable error code. **400 Bad Request** - Missing or invalid URL parameter: ```json { "error": "Missing or invalid query parameter: url", "code": "INVALID_URL" } ``` **502 Bad Gateway** - Failed to fetch webpage or AI processing error: ```json { "error": "Failed to fetch or summarize", "code": "FETCH_OR_AI_ERROR" } ``` ## Implementation Details [#implementation-details] ### AI Model Configuration [#ai-model-configuration] The worker uses Workers AI with the following configuration: ```typescript // From src/constants/defaults.ts export const AI_MODEL = "@cf/meta/llama-3.1-8b-instruct-fast"; export const MAX_SUMMARY_TOKENS = 4096; export const MAX_HTML_BYTES = 128 * 1024; // 128KB limit export const FETCH_TIMEOUT_MS = 15_000; // 15 second timeout ``` ### Request Flow [#request-flow] The summarization process follows these steps: <Steps> <Step> ### Validate URL [#validate-url] Ensures the URL uses http/https scheme and is properly formatted: ```typescript const url = validateUrl(c.req.query("url")); if (!url) return jsonError(c, "Missing or invalid query parameter: url", "INVALID_URL"); ``` </Step> <Step> ### Fetch HTML [#fetch-html] Retrieves the webpage with a 15-second timeout and 128KB size limit: ```typescript const html = await fetchHtml(url); ``` </Step> <Step> ### Extract Content [#extract-content] Parses the HTML to extract title and body text: ```typescript const title = getTitle(html); const bodyText = getBodyText(html, 6000); // Up to 6000 chars ``` </Step> <Step> ### Generate Summary [#generate-summary] Sends content to Workers AI for summarization: ```typescript const summary = await summarizeWithAi(c.env, bodyText, title); ``` The AI prompt instructs the model to: * Generate exactly 2-4 concise sentences * Return only the summary (no preamble or labels) * Focus on key information from the page </Step> <Step> ### Clean Response [#clean-response] Removes any boilerplate phrases the model may add: ```typescript // Strips patterns like "Here is a summary:" or "Let me know if..." return extractSummaryOnly(response); ``` </Step> </Steps> ### Core Implementation [#core-implementation] Here's the main route handler from `src/routes/summary.ts`: ```typescript app.get("/summary", async (c) => { const url = validateUrl(c.req.query("url")); if (!url) return jsonError(c, "Missing or invalid query parameter: url", "INVALID_URL"); try { const html = await fetchHtml(url); const title = getTitle(html); const bodyText = getBodyText(html, 6000); if (!bodyText.trim()) { return jsonSuccess<SummaryResponse>(c, { title, summary: "No extractable text content." }); } const summary = await summarizeWithAi(c.env, bodyText, title); const response: SummaryResponse = { title, summary }; return jsonSuccess(c, response); } catch (e) { const message = e instanceof Error ? e.message : "Failed to fetch or summarize"; return jsonError(c, message, "FETCH_OR_AI_ERROR", 502); } }); ``` ## Use Cases [#use-cases] * **Content Curation** - Automatically generate summaries for bookmarking tools * **Research Assistants** - Quickly preview webpage content before visiting * **News Aggregators** - Create summaries for article feeds * **Browser Extensions** - Add "summarize this page" functionality * **Slack/Discord Bots** - Share link summaries in team chats ## Limitations [#limitations] * **JavaScript-heavy sites**: Only static HTML is fetched; JavaScript-rendered content is not executed * **Rate limits**: Workers AI has [usage limits](https://developers.cloudflare.com/workers-ai/platform/limits/) based on your plan * **Fetch timeout**: Requests that take longer than 15 seconds will fail * **Size limit**: HTML content is capped at 128KB; body text is truncated to 6000 characters ## Deployment [#deployment] <Steps> <Step> ### Click the deploy button [#click-the-deploy-button] [![Deploy to Cloudflare Workers](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/shrinathsnayak/cloudflare-experiments/tree/main/apps/experiments/ai-website-summary) </Step> <Step> ### Deploy [#deploy] Enable the **Workers AI** binding (`AI`) in your Worker settings. The deploy button configures this automatically via `wrangler.json`. Requires a Cloudflare account with Workers AI enabled. </Step> <Step> ### Test your deployment [#test-your-deployment] ```bash curl "https://your-worker.workers.dev/summary?url=https://www.cloudflare.com" ``` </Step> </Steps> ## Local Development [#local-development] ```bash cd apps/experiments/ai-website-summary npm install npm run dev ``` Test locally: ```bash curl "http://localhost:8787/summary?url=https://www.cloudflare.com" ``` ## Configuration [#configuration] The Worker automatically binds to Workers AI. The `wrangler.json` configuration includes: ```json { "name": "ai-website-summary", "main": "src/index.ts", "compatibility_date": "2024-01-01", "ai": { "binding": "AI" } } ``` No additional environment variables or secrets are required. ### Dependencies [#dependencies] ```json { "dependencies": { "hono": "^4.6.12" }, "devDependencies": { "@cloudflare/workers-types": "^4.20241127.0", "typescript": "^5.7.2", "wrangler": "^4" } } ``` ## Cloudflare Features Used [#cloudflare-features-used] * **[Workers](https://developers.cloudflare.com/workers/)** - Serverless execution environment * **[Workers AI](https://developers.cloudflare.com/workers-ai/)** - Run LLMs on the edge with the `AI` binding * **[Fetch API](https://developers.cloudflare.com/workers/runtime-apis/fetch/)** - HTTP client for fetching webpages ## Next Steps [#next-steps] <Cards> <Card title="Workers AI Models" href="https://developers.cloudflare.com/workers-ai/models/"> Explore other models available in Workers AI </Card> <Card title="Hono Framework" href="https://hono.dev"> Learn more about the Hono web framework </Card> <Card title="GitHub Repository" href="https://github.com/shrinathsnayak/cloudflare-experiments/tree/main/apps/experiments/ai-website-summary"> View the complete source code </Card> <Card title="More Experiments" href="/"> Explore other Cloudflare experiments </Card> </Cards> # AI Website Tag Generator (/docs/experiments/ai-website-tag-generator) <Callout type="warning"> This is an experimental Worker. Use it as a starting point for your own projects. </Callout> The AI Website Tag Generator experiment fetches any webpage, extracts its content, and uses [Workers AI](https://developers.cloudflare.com/workers-ai/) to generate topic tags. Built with Hono and TypeScript, it runs on Cloudflare's edge network-no external API keys required. ## Features [#features] * Fetch and parse any public webpage * Extract title and body text from HTML * Generate topic tags using Workers AI (e.g. Llama 3.1) * Sub-second responses on the edge * Accepts full URLs or hostnames (e.g. `https://cloudflare.com` or `example.com`) ## API Reference [#api-reference] ### GET /tags [#get-tags] Generate topic tags for any webpage. <TypeTable type="{ url: { description: "The webpage URL or hostname to tag. Use `http://` or `https://` for full URLs, or a bare hostname like `example.com`.", type: "string", required: true, }, }" /> #### Response [#response] **`tags`** `string[]` An array of topic tags generated from the page content. #### Example Request [#example-request] ```bash curl "https://your-worker.workers.dev/tags?url=https://www.cloudflare.com" ``` #### Example Response [#example-response] ```json { "tags": ["technology", "CDN", "security", "edge computing", "DDoS protection"] } ``` #### Error Responses [#error-responses] **`error`** `string` Human-readable error message. **`code`** `string` Machine-readable error code. **400 Bad Request** - Missing or invalid URL: ```json { "error": "Missing or invalid query parameter: url", "code": "INVALID_URL" } ``` **502 Bad Gateway** - Failed to fetch webpage or AI error: ```json { "error": "Failed to fetch or generate tags", "code": "FETCH_OR_AI_ERROR" } ``` ## Use Cases [#use-cases] * **Content tagging** - Auto-tag articles or bookmarks by topic * **SEO and discovery** - Suggest categories or related topics from page content * **Link previews** - Enrich shared links with topic tags * **Research and curation** - Quickly label pages by theme ## Limitations [#limitations] * **JavaScript-heavy sites** - Only static HTML is fetched; client-rendered content is not executed * **Workers AI limits** - Subject to [Workers AI usage limits](https://developers.cloudflare.com/workers-ai/platform/limits/) by plan * **Fetch and size limits** - Same constraints as other URL-based experiments (timeout, HTML size cap) ## Deployment [#deployment] <Steps> <Step> ### Click the deploy button [#click-the-deploy-button] [![Deploy to Cloudflare Workers](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/shrinathsnayak/cloudflare-experiments/tree/main/apps/experiments/ai-website-tag-generator) </Step> <Step> ### Deploy [#deploy] Enable the **Workers AI** binding (`AI`) in your Worker settings. The deploy button configures this automatically via `wrangler.json`. Requires a Cloudflare account with Workers AI enabled. </Step> <Step> ### Test your deployment [#test-your-deployment] ```bash curl "https://your-worker.workers.dev/tags?url=https://www.cloudflare.com" ``` </Step> </Steps> ## Local Development [#local-development] ```bash cd apps/experiments/ai-website-tag-generator npm install npm run dev ``` Test locally: ```bash curl "http://localhost:8787/tags?url=https://www.cloudflare.com" ``` ## Cloudflare Features Used [#cloudflare-features-used] * **[Workers](https://developers.cloudflare.com/workers/)** - Serverless execution environment * **[Workers AI](https://developers.cloudflare.com/workers-ai/)** - Run LLMs on the edge with the `AI` binding * **[Fetch API](https://developers.cloudflare.com/workers/runtime-apis/fetch/)** - HTTP client for fetching webpages ## Next Steps [#next-steps] <Cards> <Card title="Workers AI Models" href="https://developers.cloudflare.com/workers-ai/models/"> Explore other models available in Workers AI </Card> <Card title="AI Website Summary" href="/experiments/ai-website-summary"> Summarize webpages with Workers AI </Card> <Card title="GitHub Repository" href="https://github.com/shrinathsnayak/cloudflare-experiments/tree/main/apps/experiments/ai-website-tag-generator"> View the complete source code </Card> <Card title="More Experiments" href="/"> Explore other Cloudflare experiments </Card> </Cards> # Analytics Engine (/docs/experiments/analytics-engine) Record custom analytics events from a Worker using [Workers Analytics Engine](https://developers.cloudflare.com/analytics/analytics-engine/). The `experiment_events` dataset is auto-created on first write - no manual dashboard setup required. ## API Reference [#api-reference] ### POST /track [#post-track] Writes a named event with an optional numeric value and tag to the Analytics Engine dataset. <TypeTable type="{ event: { description: "Event name (non-empty string, max 100 characters)", type: "string", required: true, }, value: { description: "Numeric value to record (defaults to `1`)", type: "number", }, tag: { description: "Optional string tag stored as a blob alongside the event", type: "string", }, }" /> #### Example Request [#example-request] ```bash curl -X POST "https://your-worker.workers.dev/track" \ -H "Content-Type: application/json" \ -d '{"event":"page_view","value":1,"tag":"homepage"}' ``` #### Success Response [#success-response] **`ok`** `boolean` Always `true` on success **`event`** `string` The event name that was recorded **`value`** `number` The numeric value that was written ```json { "ok": true, "event": "page_view", "value": 1 } ``` #### Error Response [#error-response] ```json { "error": "Missing or invalid field: event (required non-empty string, max 100 characters)", "code": "INVALID_EVENT" } ``` #### Error Codes [#error-codes] * `400` - Invalid JSON body (`INVALID_BODY`) * `400` - Missing or invalid `event` (`INVALID_EVENT`) ## Use Cases [#use-cases] * Emit custom metrics from edge Workers without a third-party analytics SDK * Track feature usage, clicks, or conversions with named events and tags * Learn Analytics Engine `writeDataPoint` in a minimal stateless API * Prototype observability pipelines before wiring dashboards and SQL queries ## Limitations [#limitations] * Writes datapoints only; no query or dashboard API in this experiment * No sampling, aggregation, or retention configuration * Requires an Analytics Engine binding in `wrangler.json` ## Deployment [#deployment] <Steps> <Step> ### Click the deploy button [#click-the-deploy-button] [![Deploy to Cloudflare Workers](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/shrinathsnayak/cloudflare-experiments/tree/main/apps/experiments/analytics-engine) </Step> <Step> ### Deploy [#deploy] The **Analytics Engine** binding (`ANALYTICS`) is configured in `wrangler.json` with dataset `experiment_events`. The dataset is created automatically when the first event is written - you do not need to create it in the dashboard first. </Step> <Step> ### Test your deployment [#test-your-deployment] ```bash curl -X POST "https://your-worker.workers.dev/track" \ -H "Content-Type: application/json" \ -d '{"event":"page_view","value":1,"tag":"homepage"}' ``` </Step> </Steps> ## Local Development [#local-development] ```bash cd apps/experiments/analytics-engine npm install npm run dev ``` Test locally: ```bash curl -X POST "http://localhost:8787/track" \ -H "Content-Type: application/json" \ -d '{"event":"demo_click","value":1,"tag":"local"}' ``` ## Cloudflare Features Used [#cloudflare-features-used] * **[Workers](https://developers.cloudflare.com/workers/)** - Edge compute runtime * **[Workers Analytics Engine](https://developers.cloudflare.com/analytics/analytics-engine/)** - `experiment_events` dataset via `ANALYTICS` binding # API Mock Server (/docs/experiments/api-mock-server) Create configurable mock API responses (path, method, status, JSON body, optional delay) stored in **Workers KV**. Any matching request to `/mock/:slug` returns the configured response. ## Features [#features] * POST /configs - create a mock and receive a slug * GET /configs - list all mocks * GET /mock/:slug - serve the configured response * DELETE /configs/:slug - remove a mock ## API Reference [#api-reference] ### POST /configs [#post-configs] Create a mock endpoint configuration. **`path`** `string` (required) - Mock path (must start with `/`). **`method`** `string` (required) - HTTP method. **`status`** `number` (required) - Response status (100–599). **`body`** `object` (required) - JSON response body. **`delayMs`** `number` (optional) - Artificial delay (0–30000 ms). ### GET /mock/:slug [#get-mockslug] Serve the mock when the request method matches the stored config. ### DELETE /configs/:slug [#delete-configsslug] Delete a mock by slug. #### Error Codes [#error-codes] * `400` - `INVALID_BODY`, validation errors * `404` - `NOT_FOUND` ## Use Cases [#use-cases] * Stub third-party APIs during frontend development * Demo API integrations without standing up a full backend * Test client retry and timeout behavior with configurable delays ## Limitations [#limitations] * Requires a KV namespace binding * No authentication on config endpoints * Mocks are global to the namespace; not multi-tenant ## Deployment [#deployment] <Steps> <Step> ### Click the deploy button [#click-the-deploy-button] [![Deploy to Cloudflare Workers](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/shrinathsnayak/cloudflare-experiments/tree/main/apps/experiments/api-mock-server) </Step> <Step> ### Configure bindings [#configure-bindings] Create a KV namespace and set its id in `wrangler.json` under `MOCKS`. </Step> <Step> ### Test your deployment [#test-your-deployment] See the experiment README for curl examples. </Step> </Steps> ## Local Development [#local-development] ```bash cd apps/experiments/api-mock-server npm install npm run dev ``` ## Configuration [#configuration] Create a KV namespace and set its id in `wrangler.json` under `MOCKS`. ## Cloudflare Features Used [#cloudflare-features-used] * **[Workers](https://developers.cloudflare.com/workers/)** * **[Workers KV](https://developers.cloudflare.com/kv/)** # Broken Link Checker (/docs/experiments/broken-link-checker) Fetch a webpage, extract `a[href]` links, and probe each URL's HTTP status from Cloudflare's edge. ## Features [#features] * Extracts absolute and same-origin relative links from HTML * Probes up to 25 links by default (max 50) with HEAD, falling back to GET * Runs probes concurrently (5 at a time) with per-link timeouts * Stateless; no bindings required ## API Reference [#api-reference] ### GET /check [#get-check] <TypeTable type="{ url: { description: "The target page URL (must be http or https)", type: "string", required: true, }, limit: { description: "Max links to probe (default 25, max 50)", type: "number", required: false, }, }" /> #### Example Request [#example-request] ```bash curl "https://your-worker.workers.dev/check?url=https://www.cloudflare.com&limit=10" ``` #### Success Response [#success-response] **`url`** `string` Final page URL after redirects **`checked`** `number` Number of links probed **`broken`** `number` Count of non-OK responses **`links`** `array` Per-link `{ href, statusCode, ok, error? }` ```json { "url": "https://www.cloudflare.com/", "checked": 10, "broken": 1, "links": [ { "href": "https://www.cloudflare.com/", "statusCode": 200, "ok": true }, { "href": "https://www.cloudflare.com/missing", "statusCode": 404, "ok": false } ] } ``` #### Error Codes [#error-codes] * `400` - Missing or invalid `url` (`INVALID_URL`) * `400` - Response was not HTML (`NOT_HTML`) * `502` - Page fetch failed (`FETCH_ERROR`) ## Use Cases [#use-cases] * Find dead links before publishing content * Spot-check navigation and footer URLs * Complement link extraction tools with live status checks * Build simple site QA workflows on Workers ## Limitations [#limitations] * Does not execute JavaScript; only links present in raw HTML * Caps probes (default 25) to stay under Worker time limits * Some origins block HEAD or bots; status codes may reflect bot filtering ## Deployment [#deployment] <Steps> <Step> ### Click the deploy button [#click-the-deploy-button] [![Deploy to Cloudflare Workers](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/shrinathsnayak/cloudflare-experiments/tree/main/apps/experiments/broken-link-checker) </Step> <Step> ### Deploy [#deploy] Follow the deployment wizard. No bindings or secrets required. </Step> <Step> ### Test your deployment [#test-your-deployment] ```bash curl "https://your-worker.workers.dev/check?url=https://example.com" ``` </Step> </Steps> ## Local Development [#local-development] ```bash cd apps/experiments/broken-link-checker npm install npm run dev ``` ```bash curl "http://localhost:8787/check?url=https://www.cloudflare.com" ``` ## Cloudflare Features Used [#cloudflare-features-used] * **[Workers](https://developers.cloudflare.com/workers/)** - Edge compute runtime * **[Fetch API](https://developers.cloudflare.com/workers/runtime-apis/fetch/)** - Page fetch and link probes ## Next Steps [#next-steps] <Cards> <Card title="Browser Links" href="/docs/experiments/browser-links"> Extract links from JavaScript-rendered pages </Card> <Card title="Is It Down" href="/docs/experiments/is-it-down"> Check if a single URL is reachable from the edge </Card> </Cards> # Browser Links (/docs/experiments/browser-links) Load a page in a headless browser and return all unique anchor links after JavaScript rendering. ## API Reference [#api-reference] ### GET /links [#get-links] <TypeTable type="{ url: { description: "The website URL to inspect (must be http or https)", type: "string", required: true, }, }" /> #### Example Request [#example-request] ```bash curl "https://your-worker.workers.dev/links?url=https://www.cloudflare.com" ``` #### Success Response [#success-response] **`url`** `string` The normalized URL that was loaded **`linkCount`** `number` Number of unique links returned **`truncated`** `boolean` Whether results were capped at 500 links **`links`** `array` Each item has `href` and `text` ```json { "url": "https://www.cloudflare.com/", "linkCount": 42, "truncated": false, "links": [{ "href": "https://www.cloudflare.com/plans/", "text": "Plans" }] } ``` #### Error Codes [#error-codes] * `400` - Missing or invalid `url` (`INVALID_URL`) * `502` - Navigation or extraction failed (`LINKS_ERROR`) ## Use Cases [#use-cases] * Crawl SPAs and client-rendered navigation * Build sitemaps from fully rendered pages * Compare static HTML links vs browser-rendered links ## Limitations [#limitations] * Requires Browser Rendering enabled on your Cloudflare account * Local development needs `npx wrangler dev --remote` to reach a real browser * Fixed 1280×800 viewport and navigation timeout * Single URL per request; no multi-page crawling ## Deployment [#deployment] <Steps> <Step> ### Click the deploy button [#click-the-deploy-button] [![Deploy to Cloudflare Workers](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/shrinathsnayak/cloudflare-experiments/tree/main/apps/experiments/browser-links) </Step> <Step> ### Enable Browser Rendering [#enable-browser-rendering] Browser Rendering must be enabled on your account. </Step> </Steps> ## Local Development [#local-development] ```bash cd apps/experiments/browser-links npm install npm run dev ``` <Callout> `npm run dev` uses `wrangler dev --remote` so a real browser instance is available. </Callout> ## Cloudflare Features Used [#cloudflare-features-used] * **[Workers](https://developers.cloudflare.com/workers/)** - Edge compute runtime * **[Browser Rendering](https://developers.cloudflare.com/browser-rendering/)** - Headless Chromium via `@cloudflare/puppeteer` # Cloud AI Proxy (/docs/experiments/cloud-ai-proxy) <Callout type="warning"> This is an experimental Worker. Use it as a starting point for your own projects. </Callout> The Cloud AI Proxy experiment exposes [Workers AI](https://developers.cloudflare.com/workers-ai/) through a single public endpoint. You pass a model ID and prompt (or chat messages) and receive AI-generated text. Use it to try different models or integrate Workers AI from external tools without managing Wrangler or bindings yourself. <Callout> The endpoint is public by design. For production use, add rate limiting or API key authentication. </Callout> ## Features [#features] * Call any Workers AI text-generation model via one endpoint * Support for single prompt or chat-style messages * Optional `max_tokens` control * POST (JSON) and GET (query params) interfaces * No external API keys; uses your Cloudflare account’s Workers AI ## API Reference [#api-reference] ### POST /chat [#post-chat] Send a JSON body with model and prompt or messages. | Field | Required | Description | | ------------ | -------- | ----------------------------------------------------------------- | | `model` | Yes | Workers AI model ID (e.g. `@cf/meta/llama-3.1-8b-instruct-fast`) | | `prompt` | No\* | Single prompt string | | `messages` | No\* | Array of `{ role: string, content: string }` for chat-style input | | `max_tokens` | No | Max tokens to generate (1–4096) | \*At least one of `prompt` or `messages` is required. **`response`** `string` The AI-generated text. #### Example Request [#example-request] ```bash curl -X POST https://your-worker.workers.dev/chat \ -H "Content-Type: application/json" \ -d '{"model":"@cf/meta/llama-3.1-8b-instruct-fast","prompt":"Say hello in one sentence."}' ``` #### Example Response [#example-response] ```json { "response": "Hello! How can I help you today?" } ``` ### GET /chat [#get-chat] Query params: `model` (required), `prompt` (required), optional `max_tokens`. <TypeTable type="{ model: { description: "Workers AI model ID (e.g. `@cf/meta/llama-3.1-8b-instruct-fast`).", type: "string", required: true, }, prompt: { description: "The prompt text. URL-encoded.", type: "string", required: true, }, }" /> #### Example Request [#example-request-1] ```bash curl "https://your-worker.workers.dev/chat?model=@cf/meta/llama-3.1-8b-instruct-fast&prompt=Say%20hello" ``` #### Error Responses [#error-responses] * **400 Bad Request** - `INVALID_BODY`, `MISSING_MODEL`, `MISSING_PROMPT`, or `INVALID_MAX_TOKENS` * **502 Bad Gateway** - `AI_ERROR` (model run failed) ## Use Cases [#use-cases] * Try Workers AI models from curl, Postman, or external tools without managing bindings * Prototype chat APIs before adding authentication and rate limiting * Compare model outputs by swapping the `model` parameter * Integrate edge AI into apps via a simple HTTP proxy ## Limitations [#limitations] * Public endpoint with no authentication or rate limiting by default * Workers AI is subject to [usage limits](https://developers.cloudflare.com/workers-ai/platform/limits/) by plan * `max_tokens` is capped at 4096 * Text generation only; no streaming responses or tool calling ## Deployment [#deployment] <Steps> <Step> ### Click the deploy button [#click-the-deploy-button] [![Deploy to Cloudflare Workers](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/shrinathsnayak/cloudflare-experiments/tree/main/apps/experiments/cloud-ai-proxy) </Step> <Step> ### Deploy [#deploy] Enable the **Workers AI** binding (`AI`) in your Worker settings. The deploy button configures this automatically via `wrangler.json`. Requires a Cloudflare account with Workers AI enabled. </Step> <Step> ### Test your deployment [#test-your-deployment] ```bash curl -X POST "https://your-worker.workers.dev/chat" \ -H "Content-Type: application/json" \ -d '{"model":"@cf/meta/llama-3.1-8b-instruct-fast","prompt":"Say hello in one sentence."}' ``` </Step> </Steps> ## Local Development [#local-development] ```bash cd apps/experiments/cloud-ai-proxy npm install npm run dev ``` Call the endpoint at `http://localhost:8787/chat` with POST (JSON body) or GET (query params). Requires a Cloudflare account with Workers AI enabled. ## Cloudflare Features Used [#cloudflare-features-used] * **[Workers](https://developers.cloudflare.com/workers/)** - Edge runtime * **[Workers AI](https://developers.cloudflare.com/workers-ai/)** - `AI` binding for text generation ## Next Steps [#next-steps] <Cards> <Card title="Workers AI models" href="https://developers.cloudflare.com/workers-ai/models/" /> <Card title="GitHub repository" href="https://github.com/shrinathsnayak/cloudflare-experiments/tree/main/apps/experiments/cloud-ai-proxy" /> </Cards> # CORS Preflight Tester (/docs/experiments/cors-preflight-tester) Send an **OPTIONS** preflight to a target URL with a simulated origin, method, and requested headers. Reports which CORS response headers are present, missing, or misconfigured relative to what the browser would require. ## Features [#features] * POST /test - simulate Access-Control-Request-Method/Headers preflight * Analyzes Allow-Origin, Allow-Methods, Allow-Headers, Allow-Credentials * Returns actionable pass/fail per header requirement ## API Reference [#api-reference] ### POST /test [#post-test] **`url`** `string` (required) - Target URL (http or https). **`origin`** `string` (required) - Simulated Origin header value. **`method`** `string` (required) - Intended request method (e.g. `POST`). **`headers`** `string[]` (optional) - Request headers for preflight (e.g. `["Content-Type"]`). #### Example Request [#example-request] ```bash curl -X POST "https://your-worker.workers.dev/test" \ -H "Content-Type: application/json" \ -d '{"url":"https://api.example.com/data","origin":"https://app.example.com","method":"POST","headers":["Content-Type"]}' ``` #### Error Codes [#error-codes] * `400` - `INVALID_BODY`, `INVALID_URL`, `INVALID_ORIGIN`, `INVALID_METHOD` * `502` - `PREFLIGHT_ERROR` ## Use Cases [#use-cases] * Debug why a browser blocks cross-origin API calls * Validate CORS headers before deploying a new frontend origin * Compare preflight behavior across staging and production APIs ## Limitations [#limitations] * Simulates preflight from Workers egress, not from an end-user browser network * Does not follow complex redirect chains for OPTIONS * Single preflight scenario per request ## Deployment [#deployment] <Steps> <Step> ### Click the deploy button [#click-the-deploy-button] [![Deploy to Cloudflare Workers](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/shrinathsnayak/cloudflare-experiments/tree/main/apps/experiments/cors-preflight-tester) </Step> <Step> ### Configure bindings [#configure-bindings] See `wrangler.json` and the experiment README for required bindings. </Step> <Step> ### Test your deployment [#test-your-deployment] See the experiment README for curl examples. </Step> </Steps> ## Local Development [#local-development] ```bash cd apps/experiments/cors-preflight-tester npm install npm run dev ``` ## Configuration [#configuration] No bindings required beyond the Workers runtime. ## Cloudflare Features Used [#cloudflare-features-used] * **[Workers](https://developers.cloudflare.com/workers/)** * **[Fetch API](https://developers.cloudflare.com/workers/runtime-apis/fetch/)** # Cron Heartbeat (/docs/experiments/cron-heartbeat) A minimal reference for **Cron Triggers**: a `scheduled()` handler runs every 5 minutes and writes run metadata to **Workers KV**. An HTTP route reads that metadata back. ## API Reference [#api-reference] ### GET /status [#get-status] Returns metadata from the most recent scheduled run. #### Example Request [#example-request] ```bash curl "https://your-worker.workers.dev/status" ``` #### Response Fields [#response-fields] **`lastRun`** `string | null` ISO timestamp of the last cron execution. **`lastCron`** `string | null` Cron expression that fired (e.g. `*/5 * * * *`). **`runCount`** `number` Total scheduled runs since deploy. #### Example Response [#example-response] ```json { "lastRun": "2025-06-20T12:00:00.000Z", "lastCron": "*/5 * * * *", "runCount": 12 } ``` <Callout> Before the first cron fires, `lastRun` and `lastCron` are `null` and `runCount` is `0`. </Callout> ## Use Cases [#use-cases] * Learn how to implement a `scheduled()` handler in Workers * Periodic health checks, cache warming, or cleanup jobs * Background maintenance without external cron services * Storing cron run history in KV for observability ## Limitations [#limitations] * Cron schedule is fixed in `wrangler.json`; change and redeploy to adjust timing * Stores last-run metadata in KV only; not a full job scheduler * Demonstration worker; no alerting or retry logic on failed runs ## Deployment [#deployment] <Steps> <Step> ### Click the deploy button [#click-the-deploy-button] [![Deploy to Cloudflare Workers](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/shrinathsnayak/cloudflare-experiments/tree/main/apps/experiments/cron-heartbeat) </Step> <Step> ### Create KV namespace [#create-kv-namespace] Update `wrangler.json` with your KV namespace ID for the `STATUS` binding. </Step> <Step> ### Check status [#check-status] ```bash curl "https://your-worker.workers.dev/status" ``` </Step> </Steps> ## Local Development [#local-development] ```bash cd apps/experiments/cron-heartbeat npm install npm run dev ``` Trigger a cron manually in local dev: ```bash curl "http://localhost:8787/cdn-cgi/handler/scheduled" ``` ## Configuration [#configuration] `wrangler.json` declares: * **Cron trigger** `*/5 * * * *` (every 5 minutes) * **KV binding** `STATUS` for run metadata ## Cloudflare Features Used [#cloudflare-features-used] * **[Workers](https://developers.cloudflare.com/workers/)** - Edge compute runtime * **[Cron Triggers](https://developers.cloudflare.com/workers/configuration/cron-triggers/)** - scheduled Worker invocations * **[Workers KV](https://developers.cloudflare.com/kv/)** - persist run metadata across invocations # Crypto Hash (/docs/experiments/crypto-hash) Compute SHA-256, SHA-384, or SHA-512 digests for any text input using the Web Crypto API in Workers. ## API Reference [#api-reference] ### GET /hash [#get-hash] Returns a hex-encoded hash for the provided text. <TypeTable type="{ text: { description: "Input string to hash (max 10,000 characters)", type: "string", required: true, }, algorithm: { description: "Hash algorithm: `SHA-256` (default), `SHA-384`, or `SHA-512`", type: "string", }, }" /> #### Example Request [#example-request] ```bash curl "https://your-worker.workers.dev/hash?text=hello&algorithm=SHA-256" ``` #### Success Response [#success-response] **`algorithm`** `string` The algorithm used for the digest **`hash`** `string` Lowercase hex-encoded digest **`inputLength`** `number` Length of the input text in characters ```json { "algorithm": "SHA-256", "hash": "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824", "inputLength": 5 } ``` #### Error Response [#error-response] ```json { "error": "Missing or invalid query parameter: text", "code": "INVALID_TEXT" } ``` #### Error Codes [#error-codes] * `400` - Missing or invalid `text` (`INVALID_TEXT`) * `400` - Unsupported `algorithm` (`INVALID_ALGORITHM`) ## Use Cases [#use-cases] * Hash strings at the edge without shipping crypto libraries * Verify content fingerprints in API workflows * Learn Web Crypto (`crypto.subtle.digest`) in Workers * Compare digest sizes across SHA-256, SHA-384, and SHA-512 ## Limitations [#limitations] * Hashes plain text only; no file upload or streaming input * No HMAC, salting, or keyed hashing in this experiment * Input size is limited by Worker memory and request limits ## Deployment [#deployment] <Steps> <Step> ### Click the deploy button [#click-the-deploy-button] [![Deploy to Cloudflare Workers](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/shrinathsnayak/cloudflare-experiments/tree/main/apps/experiments/crypto-hash) </Step> <Step> ### Deploy [#deploy] No additional configuration required. </Step> <Step> ### Test your deployment [#test-your-deployment] ```bash curl "https://your-worker.workers.dev/hash?text=hello" ``` </Step> </Steps> ## Local Development [#local-development] ```bash cd apps/experiments/crypto-hash npm install npm run dev ``` Test locally: ```bash curl "http://localhost:8787/hash?text=hello" ``` ## Cloudflare Features Used [#cloudflare-features-used] * **[Workers](https://developers.cloudflare.com/workers/)** - Edge compute runtime * **[Web Crypto API](https://developers.cloudflare.com/workers/runtime-apis/web-crypto/)** - `crypto.subtle.digest` for SHA hashing # D1 SQL Playground (/docs/experiments/d1-sql-playground) Run safe, read-only `SELECT` queries against a seeded Cloudflare D1 database. Returns JSON rows, inferred column metadata, and query timing. ## Features [#features] * **POST /query** - Execute a validated `SELECT` and receive rows plus column types * **Seeded schema** - `products` catalog and `experiments` table from this repo * **Read-only guardrails** - Rejects writes, DDL, semicolons, comments, `UNION`, and disallowed tables * **GET /** - Lists queryable tables ## API Reference [#api-reference] ### POST /query [#post-query] Execute a single read-only `SELECT` against the seeded database. <TypeTable type="{ sql: { description: "One `SELECT` statement. Must reference only `products` or `experiments`. No semicolons, SQL comments, or multiple statements.", type: "string", required: true, }, }" /> #### Example Request [#example-request] ```bash curl -X POST "https://your-worker.workers.dev/query" \ -H "Content-Type: application/json" \ -d '{"sql":"SELECT name, price FROM products WHERE in_stock = 1 ORDER BY price DESC LIMIT 5"}' ``` #### Success Response [#success-response] ```json { "columns": [ { "name": "name", "type": "text" }, { "name": "price", "type": "number" } ], "rows": [ { "name": "Browser Rendering Frame", "price": 59.99 }, { "name": "Workers AI Prompt Pack", "price": 49.99 } ], "rowCount": 2, "durationMs": 3 } ``` #### Error Codes [#error-codes] * `400` - Invalid JSON (`INVALID_BODY`) or disallowed SQL (`INVALID_SQL`) * `502` - D1 execution error (`QUERY_ERROR`) ### GET / [#get-] Returns app metadata and the list of allowed tables (`products`, `experiments`). ## Seeded Tables [#seeded-tables] | Table | Columns (high level) | | ------------- | ----------------------------------------------------------- | | `products` | `id`, `name`, `category`, `price`, `in_stock`, `created_at` | | `experiments` | `slug`, `name`, `category`, `description` | Example queries: ```sql SELECT category, COUNT(*) AS total FROM products GROUP BY category SELECT slug, name FROM experiments WHERE category = 'Storage & Data' SELECT p.name, e.name FROM products p JOIN experiments e ON p.category LIKE '%' || e.category || '%' LIMIT 5 ``` ## Use Cases [#use-cases] * Learn D1 query patterns without provisioning your own schema * Prototype read-only analytics APIs over SQLite at the edge * Teach SQL safely with guardrails against writes and injection * Explore JOINs and aggregations on realistic sample data ## Limitations [#limitations] * Read-only: `INSERT`, `UPDATE`, `DELETE`, DDL, and `UNION` are rejected * Only `products` and `experiments` tables are queryable * Column types are inferred from the first result row (empty results return no column metadata) * Requires D1 migrations before first use (local and remote) ## Deployment [#deployment] <Steps> <Step> ### Click the deploy button [#click-the-deploy-button] [![Deploy to Cloudflare Workers](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/shrinathsnayak/cloudflare-experiments/tree/main/apps/experiments/d1-sql-playground) </Step> <Step> ### Configure D1 [#configure-d1] Create a D1 database, set `database_id` in `wrangler.json`, then apply migrations: ```bash npm run db:migrate ``` </Step> <Step> ### Test your deployment [#test-your-deployment] ```bash curl -X POST "https://your-worker.workers.dev/query" \ -H "Content-Type: application/json" \ -d '{"sql":"SELECT * FROM products LIMIT 3"}' ``` </Step> </Steps> ## Local Development [#local-development] ```bash cd apps/experiments/d1-sql-playground npm install npm run db:migrate:local npm run dev ``` Test locally: ```bash curl -X POST "http://localhost:8787/query" \ -H "Content-Type: application/json" \ -d '{"sql":"SELECT * FROM experiments LIMIT 5"}' ``` ## Configuration [#configuration] | Binding | Purpose | | ------- | ------------------------------------ | | `DB` | D1 database (`d1-sql-playground-db`) | Migrations live in `migrations/0000_init.sql`. Apply with `npm run db:migrate:local` (dev) or `npm run db:migrate` (remote). ## Cloudflare Features Used [#cloudflare-features-used] * **[Workers](https://developers.cloudflare.com/workers/)** - Edge compute runtime * **[D1](https://developers.cloudflare.com/d1/)** - SQLite database at the edge with seeded read-only playground data # Dependency Analyzer (/docs/experiments/dependency-analyzer) Analyze all external resources and dependencies loaded by any webpage. Discover scripts, stylesheets, images, fonts, and iframes to understand a site's external dependencies and third-party integrations. ## API Reference [#api-reference] ### GET /analyze [#get-analyze] Analyze all external dependencies of a webpage by providing its URL. <TypeTable type="{ url: { description: "The URL of the webpage to analyze. Must be a valid HTTP or HTTPS URL.", type: "string", required: true, }, }" /> #### Example Request [#example-request] ```bash curl "https://your-worker.workers.dev/analyze?url=https://www.cloudflare.com" ``` #### Response Structure [#response-structure] **`success`** `boolean` Indicates if the request was successful **`data`** `object` The analyzed dependencies from the webpage **`data.scripts`** `string[]` Array of unique absolute URLs of JavaScript files loaded via `<script src="...">`. Includes all external scripts referenced in the HTML. **`data.stylesheets`** `string[]` Array of unique absolute URLs of CSS files loaded via `<link rel="stylesheet" href="...">`. Includes all external stylesheets. **`data.images`** `string[]` Array of unique absolute URLs of images. Extracts from both `<img src="...">` and `<source src="...">` tags. **`data.fonts`** `string[]` Array of unique absolute URLs of font files. Extracts from: - `<link rel="preload" as="font" href="...">` - CSS `url()` declarations for .woff, .woff2, .ttf, .otf, and .eot files **`data.iframes`** `string[]` Array of unique absolute URLs of embedded iframes via `<iframe src="...">`. Useful for identifying third-party embeds. #### Example Response [#example-response] ```json { "success": true, "data": { "scripts": [ "https://www.cloudflare.com/js/main.bundle.js", "https://www.googletagmanager.com/gtag/js?id=UA-12345678", "https://static.cloudflareinsights.com/beacon.min.js", "https://ajax.cloudflare.com/cdn-cgi/scripts/analytics.js" ], "stylesheets": [ "https://www.cloudflare.com/css/main.css", "https://www.cloudflare.com/css/responsive.css", "https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700" ], "images": [ "https://www.cloudflare.com/img/logo.svg", "https://www.cloudflare.com/img/hero-background.jpg", "https://imagedelivery.net/abc123/hero.png/public" ], "fonts": [ "https://www.cloudflare.com/fonts/inter-regular.woff2", "https://www.cloudflare.com/fonts/inter-bold.woff2", "https://fonts.gstatic.com/s/inter/v12/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa1ZL7.woff2" ], "iframes": [ "https://www.youtube.com/embed/dQw4w9WgXcQ", "https://player.vimeo.com/video/123456789" ] } } ``` ## Error Responses [#error-responses] ### Invalid URL [#invalid-url] ```json { "success": false, "error": "Missing or invalid query parameter: url", "code": "INVALID_URL" } ``` ### Fetch Error [#fetch-error] ```json { "success": false, "error": "HTTP 404", "code": "FETCH_ERROR" } ``` ## Technical Details [#technical-details] * Built with [Hono](https://hono.dev/) framework * Runs on Cloudflare Workers * Regex-based HTML parsing for fast extraction * Automatically resolves relative URLs to absolute URLs * Deduplicates all resource URLs * Extracts fonts from both link tags and CSS url() declarations ## Extraction Details [#extraction-details] ### Scripts [#scripts] * Extracted from `<script src="...">` tags * Includes inline and external JavaScript references * Does not include inline script content ### Stylesheets [#stylesheets] * Extracted from `<link rel="stylesheet" href="...">` * Includes both standard and alternate stylesheets * Matches various ordering of rel and href attributes ### Images [#images] * Extracted from `<img src="...">` tags * Also extracts from `<source src="...">` for responsive images * All formats supported (jpg, png, svg, gif, webp, etc.) ### Fonts [#fonts] * Extracted from `<link rel="preload" as="font">` or `<link rel="stylesheet" as="font">` * Also extracts from CSS `url()` declarations matching font extensions: * .woff and .woff2 (Web Open Font Format) * .ttf (TrueType Font) * .otf (OpenType Font) * .eot (Embedded OpenType) ### Iframes [#iframes] * Extracted from `<iframe src="...">` tags * Useful for identifying embeds from YouTube, Vimeo, Google Maps, etc. * Includes both same-origin and cross-origin iframes ## Notes [#notes] * All resource URLs are deduplicated across each category * Relative URLs are resolved using the base URL of the page * The tool fetches and analyzes the initial HTML only (does not execute JavaScript) * Resources loaded dynamically after page load are not captured ## Use Cases [#use-cases] * **Dependency Audits**: Identify all third-party dependencies and external resources * **Performance Analysis**: Discover resource-heavy pages loading too many external files * **Security Reviews**: Find all external scripts and potential security risks * **Privacy Compliance**: Identify third-party trackers and analytics scripts * **License Compliance**: Discover all fonts and resources that may require licensing * **CDN Optimization**: Identify candidates for CDN migration or consolidation * **Competitive Analysis**: Understand what services and tools competitors use ## Limitations [#limitations] * Static HTML only; assets loaded dynamically by JavaScript are not detected * HTML body is capped before parsing * Fetch timeout on slow or unreachable origins * Single URL per request; no site-wide crawling ## Deployment [#deployment] <Steps> <Step> ### Click the deploy button [#click-the-deploy-button] [![Deploy to Cloudflare Workers](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/shrinathsnayak/cloudflare-experiments/tree/main/apps/experiments/dependency-analyzer) </Step> <Step> ### Deploy [#deploy] No additional configuration required. </Step> <Step> ### Test your deployment [#test-your-deployment] ```bash curl "https://your-worker.workers.dev/analyze?url=https://example.com" ``` </Step> </Steps> ## Local Development [#local-development] ```bash cd apps/experiments/dependency-analyzer npm install npm run dev ``` Test locally: ```bash curl "http://localhost:8787/analyze?url=https://example.com" ``` ## Cloudflare Features Used [#cloudflare-features-used] * **[Workers](https://developers.cloudflare.com/workers/)** - Edge compute runtime * **[Fetch API](https://developers.cloudflare.com/workers/runtime-apis/fetch/)** - Remote HTML retrieval and parsing # DNS Propagation Checker (/docs/experiments/dns-propagation-checker) Query **DNS-over-HTTPS** resolvers (Cloudflare 1.1.1.1, Google 8.8.8.8, Quad9) in parallel for a domain and record type. Reports whether answers agree, each resolver's values, and response time. ## Features [#features] * GET /check - parallel DoH queries across three public resolvers * Supports A, AAAA, CNAME, MX, TXT, NS record types * Agreement summary plus per-resolver values and latency ## API Reference [#api-reference] ### GET /check [#get-check] **`domain`** `string` (required) - Hostname to query (no scheme). **`type`** `string` (required) - Record type: `A`, `AAAA`, `CNAME`, `MX`, `TXT`, or `NS`. #### Example Request [#example-request] ```bash curl "https://your-worker.workers.dev/check?domain=example.com&type=A" ``` #### Success Response [#success-response] ```json { "domain": "example.com", "type": "A", "agreement": true, "resolvers": [ { "name": "Cloudflare", "values": ["93.184.216.34"], "responseTimeMs": 42, "error": null } ] } ``` #### Error Codes [#error-codes] * `400` - `INVALID_DOMAIN`, `INVALID_TYPE` * `502` - `DNS_ERROR` ## Use Cases [#use-cases] * Verify DNS changes have propagated globally after a cutover * Compare resolver behavior during TTL or nameserver migrations * Debug split-horizon or stale cache issues across providers ## Limitations [#limitations] * Queries three public DoH endpoints only; not exhaustive global propagation * Single record type per request * Resolver errors return partial results rather than failing the whole request ## Deployment [#deployment] <Steps> <Step> ### Click the deploy button [#click-the-deploy-button] [![Deploy to Cloudflare Workers](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/shrinathsnayak/cloudflare-experiments/tree/main/apps/experiments/dns-propagation-checker) </Step> <Step> ### Configure bindings [#configure-bindings] See `wrangler.json` and the experiment README for required bindings. </Step> <Step> ### Test your deployment [#test-your-deployment] See the experiment README for curl examples. </Step> </Steps> ## Local Development [#local-development] ```bash cd apps/experiments/dns-propagation-checker npm install npm run dev ``` ## Configuration [#configuration] No bindings required beyond the Workers runtime. ## Cloudflare Features Used [#cloudflare-features-used] * **[Workers](https://developers.cloudflare.com/workers/)** * **[DNS over HTTPS](https://developers.cloudflare.com/1.1.1.1/encryption/dns-over-https/)** # DO Alarm Scheduler (/docs/experiments/do-alarm-scheduler) Schedule a one-off reminder **N seconds in the future** using the Durable Object **Alarm API** (`setAlarm`). Poll `GET /status/:id` to confirm when the alarm fired - since there is no push channel in this demo, polling is the intended pattern. ## Features [#features] * **POST /schedule** - Schedule a reminder with `{ seconds, message }` * **GET /status/:id** - Poll for `scheduled` or `fired` status * **Alarm API** - `storage.setAlarm()` + `alarm()` handler * **One DO per schedule** - Each schedule id maps to its own Durable Object instance ## API Reference [#api-reference] ### POST /schedule [#post-schedule] Schedule a one-off reminder. <TypeTable type="{ seconds: { description: "Delay before the alarm fires (1–300 seconds).", type: "number", required: true, }, message: { description: "Reminder message (max 500 characters).", type: "string", required: true, }, }" /> #### Example Request [#example-request] ```bash curl -X POST "https://your-worker.workers.dev/schedule" \ -H "Content-Type: application/json" \ -d '{"seconds":30,"message":"Check the deploy"}' ``` #### Success Response (201) [#success-response-201] ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "message": "Check the deploy", "seconds": 30, "status": "scheduled", "scheduledFor": "2025-06-20T12:00:30.000Z" } ``` #### Error Codes [#error-codes] * `400` - Invalid JSON (`INVALID_BODY`), seconds (`INVALID_SECONDS`), or message (`INVALID_MESSAGE`) * `502` - Schedule failed (`SCHEDULE_ERROR`) ### GET /status/:id [#get-statusid] Poll schedule status. After the alarm fires, `status` becomes `fired` and `firedAt` is set. #### Example Request [#example-request-1] ```bash curl "https://your-worker.workers.dev/status/550e8400-e29b-41d4-a716-446655440000" ``` #### Success Response (scheduled) [#success-response-scheduled] ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "message": "Check the deploy", "seconds": 30, "status": "scheduled", "scheduledFor": "2025-06-20T12:00:30.000Z" } ``` #### Success Response (fired) [#success-response-fired] ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "message": "Check the deploy", "seconds": 30, "status": "fired", "scheduledFor": "2025-06-20T12:00:30.000Z", "firedAt": "2025-06-20T12:00:30.123Z" } ``` #### Error Codes [#error-codes-1] * `400` - Missing id (`INVALID_ID`) * `404` - Schedule not found (`NOT_FOUND`) ## Use Cases [#use-cases] * Learn Durable Object Alarm API (`setAlarm`, `alarm()` handler) * Prototype delayed tasks without Cron Triggers or external schedulers * Build polling-based reminder demos before adding webhooks or email ## Limitations [#limitations] * Max delay 300 seconds (5 minutes) for the demo * No push notification when alarm fires; client must poll * One alarm per Durable Object instance (one schedule id) ## Deployment [#deployment] <Steps> <Step> ### Click the deploy button [#click-the-deploy-button] [![Deploy to Cloudflare Workers](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/shrinathsnayak/cloudflare-experiments/tree/main/apps/experiments/do-alarm-scheduler) </Step> <Step> ### Deploy [#deploy] Durable Object migration for `AlarmScheduler` is included in `wrangler.json`. </Step> <Step> ### Test scheduling [#test-scheduling] ```bash curl -X POST "https://your-worker.workers.dev/schedule" \ -H "Content-Type: application/json" \ -d '{"seconds":10,"message":"Hello from the alarm"}' ``` </Step> </Steps> ## Local Development [#local-development] ```bash cd apps/experiments/do-alarm-scheduler npm install npm run dev ``` Poll status after the delay: ```bash curl "http://localhost:8787/status/YOUR_SCHEDULE_ID" ``` ## Configuration [#configuration] | Binding | Purpose | | ----------- | ------------------------------------------------- | | `SCHEDULER` | Durable Object namespace (`AlarmScheduler` class) | ## Cloudflare Features Used [#cloudflare-features-used] * **[Workers](https://developers.cloudflare.com/workers/)** - Edge compute runtime * **[Durable Objects](https://developers.cloudflare.com/durable-objects/)** - Per-schedule state and alarms * **Alarm API** - One-off delayed execution via `setAlarm()` # Durable Counter (/docs/experiments/durable-counter) A minimal reference for **Durable Objects**: a single `Counter` class with persistent storage, exposed via HTTP routes. Use this pattern when you need strongly consistent state at the edge (rate limits, sessions, coordination). ## API Reference [#api-reference] ### GET /counter [#get-counter] Returns the current counter value. #### Example Request [#example-request] ```bash curl "https://your-worker.workers.dev/counter" ``` #### Example Response [#example-response] ```json { "value": 42 } ``` ### POST /counter/increment [#post-counterincrement] Increments the counter by 1. ```bash curl -X POST "https://your-worker.workers.dev/counter/increment" ``` ### POST /counter/reset [#post-counterreset] Resets the counter to 0. ```bash curl -X POST "https://your-worker.workers.dev/counter/reset" ``` ## Use Cases [#use-cases] * Learn how to define and export a Durable Object class * Shared counters or rate limiters across all edge locations * Starting point for session storage or real-time coordination * Understanding `idFromName()` for addressing a single global object ## Limitations [#limitations] * Demo uses a single global counter via `idFromName()`; not sharded for high throughput * Requires a Durable Objects binding and migration * No authentication; any client can increment or read the counter ## Deployment [#deployment] <Steps> <Step> ### Click the deploy button [#click-the-deploy-button] [![Deploy to Cloudflare Workers](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/shrinathsnayak/cloudflare-experiments/tree/main/apps/experiments/durable-counter) </Step> <Step> ### Test the API [#test-the-api] ```bash curl "https://your-worker.workers.dev/counter" curl -X POST "https://your-worker.workers.dev/counter/increment" ``` </Step> </Steps> ## Local Development [#local-development] ```bash cd apps/experiments/durable-counter npm install npm run dev ``` ## Configuration [#configuration] `wrangler.json` declares: * **Durable Object binding** `COUNTER` → class `Counter` * **Migration** `v1` registering the `Counter` class ## Cloudflare Features Used [#cloudflare-features-used] * [Durable Objects](https://developers.cloudflare.com/durable-objects/) - strongly consistent state and storage * [Workers](https://developers.cloudflare.com/workers/) - HTTP routes that call into the DO stub # Edge Cache (/docs/experiments/edge-cache) Fetch external URLs through the Workers Cache API and report whether each response was served from cache, fetched from origin, or bypassed. ## API Reference [#api-reference] ### GET /fetch [#get-fetch] Fetches a URL and returns cache metadata without streaming the full response body back to the client. <TypeTable type="{ url: { description: "The target URL to fetch (must be http or https)", type: "string", required: true, }, bypass: { description: "Set to `1`, `true`, or `yes` to skip cache lookup and storage", type: "string", }, }" /> #### Example Request [#example-request] ```bash curl "https://your-worker.workers.dev/fetch?url=https://www.cloudflare.com" ``` #### Success Response [#success-response] **`url`** `string` The normalized URL that was fetched **`cacheStatus`** `string` One of `"HIT"`, `"MISS"`, or `"BYPASS"` **`statusCode`** `number` HTTP status code from the cached or origin response **`contentType`** `string | null` Response `Content-Type` header, if present **`bodySize`** `number` Response body size in bytes ```json { "url": "https://www.cloudflare.com/", "cacheStatus": "MISS", "statusCode": 200, "contentType": "text/html; charset=utf-8", "bodySize": 125678 } ``` #### Error Response [#error-response] ```json { "error": "Missing or invalid query parameter: url", "code": "INVALID_URL" } ``` #### Error Codes [#error-codes] * `400` - Missing or invalid `url` parameter (not http/https) * `502` - Upstream fetch failed (`FETCH_ERROR`) ## Use Cases [#use-cases] * Learn how to use `caches.default` in Workers * Cache expensive origin fetches at the edge * Compare cache hit vs miss behavior during development * Build read-through cache patterns for APIs and static content ## Limitations [#limitations] * Uses the default cache with no custom TTL or cache-key logic in this demo * Cache is per-colo; entries are not globally consistent * Only caches successful GET fetch responses for the requested URL ## Deployment [#deployment] <Steps> <Step> ### Click the deploy button [#click-the-deploy-button] [![Deploy to Cloudflare Workers](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/shrinathsnayak/cloudflare-experiments/tree/main/apps/experiments/edge-cache) </Step> <Step> ### Deploy [#deploy] No additional configuration required. </Step> <Step> ### Test your deployment [#test-your-deployment] ```bash curl "https://your-worker.workers.dev/fetch?url=https://www.cloudflare.com" ``` Call the same URL again to see `"cacheStatus": "HIT"`. </Step> </Steps> ## Local Development [#local-development] ```bash cd apps/experiments/edge-cache npm install npm run dev ``` Test locally: ```bash curl "http://localhost:8787/fetch?url=https://www.cloudflare.com" ``` ## Cloudflare Features Used [#cloudflare-features-used] * **[Workers](https://developers.cloudflare.com/workers/)** - Edge compute runtime * **[Cache API](https://developers.cloudflare.com/workers/runtime-apis/cache/)** - Edge response caching via `caches.default` * **[Fetch API](https://developers.cloudflare.com/workers/runtime-apis/fetch/)** - Origin requests from Workers # Edge Redirect Simulator (/docs/experiments/edge-redirect-simulator) Show redirect chains for any URL from Cloudflare's edge. The Worker follows HTTP redirects (301, 302, 303, 307, 308) and returns each hop with its status code, so you can see exactly how a URL resolves. ## API Reference [#api-reference] ### GET /redirect-chain [#get-redirect-chain] Follow redirects for a URL and return the chain of hops. <TypeTable type="{ url: { description: "The URL to trace. Use a full URL (`https://cloudflare.com`) or hostname (`example.com`). Only `http` and `https` are allowed.", type: "string", required: true, }, }" /> #### Example Request [#example-request] ```bash curl "https://your-worker.workers.dev/redirect-chain?url=https://example.com" ``` #### Success Response [#success-response] **`chain`** `array` Array of objects, one per hop in the redirect chain. **`url`** `string` The URL at this hop (after redirect, if any). **`status`** `number` HTTP status code at this hop (e.g. 301, 302, 200). **`error`** `string` (optional) Present if the chain was truncated-e.g. missing `Location` header, redirect loop, or max redirects exceeded. Response is still 200 with the chain collected so far. #### Example Response [#example-response] ```json { "chain": [ { "url": "https://example.com/", "status": 301 }, { "url": "https://www.example.com/", "status": 302 }, { "url": "https://final.example.com/", "status": 200 } ] } ``` Display form: `example.com → 301` → `www.example.com → 302` → `final.example.com → 200`. #### Error Responses [#error-responses] **400 Bad Request** - Missing or invalid `url` (wrong scheme or malformed): ```json { "error": "Missing or invalid query parameter: url", "code": "INVALID_URL" } ``` **500 Internal Server Error** - Uncaught error (e.g. fetch failure): ```json { "error": "Internal error message", "code": "INTERNAL_ERROR" } ``` If the chain hits a redirect with no `Location` or exceeds the hop limit, the response is still **200** with the `chain` collected so far and an `error` message in the body. ## Use Cases [#use-cases] * **Debug redirects** - See the full chain from short URL or vanity domain to final destination * **SEO and canonicalization** - Verify 301/302 chains and final URLs * **Security and compliance** - Audit where redirects lead before clicking * **Integration testing** - Confirm redirect behavior from the edge ## Limitations [#limitations] * Stops after 20 redirects to prevent infinite loops * Does not follow meta refresh or JavaScript-based redirects * Fetch timeout applies per hop on slow origins * Reports redirect chain from the edge; results may differ from a browser ## Deployment [#deployment] <Steps> <Step> ### Click the deploy button [#click-the-deploy-button] [![Deploy to Cloudflare Workers](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/shrinathsnayak/cloudflare-experiments/tree/main/apps/experiments/edge-redirect-simulator) </Step> <Step> ### Deploy [#deploy] Follow the deployment wizard to deploy the Worker to your Cloudflare account. No additional configuration or bindings required. </Step> <Step> ### Test your deployment [#test-your-deployment] ```bash curl "https://your-worker.workers.dev/redirect-chain?url=https://cloudflare.com" ``` </Step> </Steps> ## Local Development [#local-development] ```bash cd apps/experiments/edge-redirect-simulator npm install npm run dev ``` Then open: `http://localhost:8787/redirect-chain?url=https://cloudflare.com` ## Cloudflare Features Used [#cloudflare-features-used] * **[Workers](https://developers.cloudflare.com/workers/)** - Edge compute runtime * **[Fetch API](https://developers.cloudflare.com/workers/runtime-apis/fetch/)** - HTTP requests with `redirect: "manual"` to capture each hop * **[Edge network](https://developers.cloudflare.com/workers/reference/how-workers-works/)** - Redirect chain observed from Cloudflare's global network ## Next Steps [#next-steps] <Cards> <Card title="Is It Down?" href="/experiments/is-it-down"> Check if a website is reachable from the edge </Card> <Card title="URL DNS Lookup" href="/experiments/url-dns-lookup"> Get DNS records for any URL's hostname </Card> <Card title="GitHub Repository" href="https://github.com/shrinathsnayak/cloudflare-experiments/tree/main/apps/experiments/edge-redirect-simulator"> View the complete source code </Card> <Card title="More Experiments" href="/"> Explore other Cloudflare experiments </Card> </Cards> # Email Auth Checker (/docs/experiments/email-auth-checker) Look up MX, SPF, DMARC, and common DKIM selectors for a domain using Cloudflare DNS over HTTPS, with pass/warn/fail guidance. ## Features [#features] * Accepts a bare domain or http(s) URL (hostname extracted) * Parses SPF and DMARC policies with actionable warnings * Probes common DKIM selectors (`google`, `selector1`, `selector2`, `default`, `k1`) * Stateless; no bindings required ## API Reference [#api-reference] ### GET /check [#get-check] <TypeTable type="{ domain: { description: "Domain name or http(s) URL whose hostname will be checked", type: "string", required: true, }, }" /> #### Example Request [#example-request] ```bash curl "https://your-worker.workers.dev/check?domain=cloudflare.com" ``` #### Success Response [#success-response] **`domain`** `string` Normalized domain that was checked **`mx`** `array` MX records with `priority` and `exchange` **`spf`** / **`dmarc`** / **`dkim`** `object` Per-record analysis with `status`, details, and optional raw records **`summary`** `object` Overall `status` (`pass` | `warn` | `fail`) and `issues` list ```json { "domain": "cloudflare.com", "mx": [{ "priority": 10, "exchange": "isaac.mx.cloudflare.net" }], "spf": { "status": "pass", "record": "v=spf1 include:_spf.mx.cloudflare.net -all", "detail": "SPF record present" }, "dmarc": { "status": "pass", "policy": "reject", "detail": "DMARC policy is reject" }, "dkim": { "status": "warn", "selectorsChecked": ["google", "selector1", "selector2", "default", "k1"], "found": [], "detail": "No DKIM keys found for common selectors..." }, "summary": { "status": "warn", "issues": ["No DKIM keys found for common selectors..."] } } ``` #### Error Codes [#error-codes] * `400` - Missing or invalid `domain` (`INVALID_DOMAIN`) * `502` - DoH lookup failed (`DNS_ERROR`) ## Use Cases [#use-cases] * Verify email authentication before sending from a new domain * Spot missing SPF/DMARC during onboarding * Explain email auth posture to non-DNS experts * Complement raw DNS dumps with actionable analysis ## Limitations [#limitations] * DKIM selectors are not enumerated exhaustively; only common ones are probed * Does not validate live mail delivery or DKIM signatures on messages * Relies on public DoH answers; DNS TTL/caching may lag recent changes ## Deployment [#deployment] <Steps> <Step> ### Click the deploy button [#click-the-deploy-button] [![Deploy to Cloudflare Workers](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/shrinathsnayak/cloudflare-experiments/tree/main/apps/experiments/email-auth-checker) </Step> <Step> ### Deploy [#deploy] Follow the deployment wizard. No bindings or secrets required. </Step> <Step> ### Test your deployment [#test-your-deployment] ```bash curl "https://your-worker.workers.dev/check?domain=example.com" ``` </Step> </Steps> ## Local Development [#local-development] ```bash cd apps/experiments/email-auth-checker npm install npm run dev ``` ```bash curl "http://localhost:8787/check?domain=cloudflare.com" ``` ## Cloudflare Features Used [#cloudflare-features-used] * **[Workers](https://developers.cloudflare.com/workers/)** - Edge compute runtime * **[DNS over HTTPS](https://developers.cloudflare.com/1.1.1.1/encryption/dns-over-https/)** - Cloudflare DoH JSON API ## Next Steps [#next-steps] <Cards> <Card title="URL DNS Lookup" href="/docs/experiments/url-dns-lookup"> Dump all DNS record types for a hostname </Card> <Card title="DNS Propagation Checker" href="/docs/experiments/dns-propagation-checker"> Compare answers across multiple public resolvers </Card> </Cards> # Email Worker Inbox (/docs/experiments/email-worker-inbox) Process inbound mail with the Cloudflare **Email Workers** `email` handler, parse MIME with `postal-mime`, store message summaries in **Workers KV**, and inspect them over HTTP. ## Features [#features] * Inbound `email` handler with reject/store policy * Parses subject/body with `postal-mime` * `GET /inbox` and `GET /inbox/:id` for inspection * Stores up to 50 recent messages with a 7-day KV TTL * Local simulation via Wrangler’s `/cdn-cgi/handler/email` endpoint ## API Reference [#api-reference] ### Email handler [#email-handler] Configured in the Cloudflare dashboard under **Email Routing** → route an address to this Worker. The demo policy rejects: * senders from `spam.example` or `@spam.` * subjects containing `[spam]` or starting with `spam:` Rejected messages are still stored in KV with `rejected: true` so you can inspect them. ### GET /inbox [#get-inbox] Lists recent stored messages (newest first, max 50). #### Example Request [#example-request] ```bash curl "https://your-worker.workers.dev/inbox" ``` #### Success Response [#success-response] **`count`** `number` Number of stored messages returned **`messages`** `array` Message summaries with `id`, `from`, `to`, `subject`, `receivedAt`, optional `text` / `html`, and rejection fields ```json { "count": 1, "messages": [ { "id": "550e8400-e29b-41d4-a716-446655440000", "from": "sender@example.com", "to": "inbox@example.com", "subject": "Hello from Email Workers", "receivedAt": "2026-07-23T12:00:00.000Z", "text": "Hello from local email simulation.", "messageId": "<test@example.com>", "rejected": false } ] } ``` ### GET /inbox/:id [#get-inboxid] Returns one stored message by id. #### Example Request [#example-request-1] ```bash curl "https://your-worker.workers.dev/inbox/550e8400-e29b-41d4-a716-446655440000" ``` #### Success Response [#success-response-1] Same shape as a single object in `messages[]` above. #### Error Response [#error-response] ```json { "error": "Message not found", "code": "NOT_FOUND" } ``` #### Error Codes [#error-codes] * `404` - Message id not found (`NOT_FOUND`) * `400` - Missing message id (`MISSING_ID`) ## Use Cases [#use-cases] * Build `support@` / `hello@` edge inboxes * Prototype spam filters before forwarding * Capture inbound mail for webhook-style automation * Teach Email Workers + KV patterns end to end ## Limitations [#limitations] * Requires Email Routing enabled on a Cloudflare zone * Stores truncated text/html previews (about 4KB each) * Messages expire after 7 days (KV TTL) * Does not forward or reply unless you extend the handler ## Deployment [#deployment] <Steps> <Step> ### Create KV namespace [#create-kv-namespace] Create a KV namespace and set its id in `wrangler.json` for the `INBOX` binding. </Step> <Step> ### Click the deploy button [#click-the-deploy-button] [![Deploy to Cloudflare Workers](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/shrinathsnayak/cloudflare-experiments/tree/main/apps/experiments/email-worker-inbox) </Step> <Step> ### Route email and test [#route-email-and-test] In Email Routing, send an address to this Worker, then inspect with: ```bash curl "https://your-worker.workers.dev/inbox" ``` </Step> </Steps> ## Local Development [#local-development] ```bash cd apps/experiments/email-worker-inbox npm install npm run dev ``` Simulate an inbound email: ```bash curl -X POST 'http://localhost:8787/cdn-cgi/handler/email?from=sender@example.com&to=inbox@example.com' \ --data-binary @- <<'EOF' From: sender@example.com To: inbox@example.com Subject: Testing Email Workers Message-ID: <test@example.com> Content-Type: text/plain Hello from local email simulation. EOF ``` Then inspect: ```bash curl "http://localhost:8787/inbox" ``` <Callout> Local email simulation uses Wrangler’s `/cdn-cgi/handler/email` helper. Production delivery requires Email Routing configured on your zone. </Callout> ## Configuration [#configuration] | Binding / dependency | Purpose | | -------------------- | ----------------------------- | | `INBOX` (KV) | Stores message index + bodies | | `postal-mime` | Parses inbound MIME messages | ## Cloudflare Features Used [#cloudflare-features-used] * **[Workers](https://developers.cloudflare.com/workers/)** - Edge compute runtime * **[Email Workers](https://developers.cloudflare.com/email-routing/email-workers/)** - Inbound email handler * **[Workers KV](https://developers.cloudflare.com/kv/)** - Message storage and TTL ## Next Steps [#next-steps] <Cards> <Card title="Email Auth Checker" href="/docs/experiments/email-auth-checker"> Analyze SPF/DMARC/DKIM for a sending domain </Card> <Card title="Webhook Relay Inspector" href="/docs/experiments/webhook-relay-inspector"> Capture inbound HTTP webhooks for debugging </Card> </Cards> # GitHub Repo Explainer (/docs/experiments/github-repo-explainer) <Callout type="warning"> This is an experimental Worker. Use it as a starting point for your own projects. </Callout> The GitHub Repo Explainer fetches README files and key configuration files from any public GitHub repository, then uses [Workers AI](https://developers.cloudflare.com/workers-ai/) to generate a comprehensive explanation including technologies used, architecture, features, and getting started instructions. ## Features [#features] * Fetch README and up to 12 key files from any public GitHub repo * Automatic prioritization of important files (package.json, tsconfig.json, etc.) * Structured AI analysis with 8 detailed sections * JSON response with technologies, features, use cases, and more * Runs entirely on Cloudflare's edge network * No GitHub authentication required (subject to rate limits) ## API Reference [#api-reference] ### GET /repo [#get-repo] Generate a comprehensive AI explanation of a GitHub repository. <TypeTable type="{ url: { description: "The GitHub repository URL. Accepts formats: - `https://github.com/owner/repo` - `https://github.com/owner/repo.git` - `owner/repo` (shorthand)", type: "string", required: true, }, }" /> #### Response [#response] **`summary`** `string` A substantive paragraph (4-6 sentences) describing what the project is, who it's for, and what problem it solves. **`mainTechnologies`** `string[]` Array of 5-15 technologies: languages, frameworks, runtimes, build tools, and key libraries (e.g., "React", "TypeScript", "Vite"). **`howItWorks`** `string` Two paragraphs explaining the architecture/flow and how to run or build the project, with concrete commands when available. **`keyFeatures`** `string[]` Array of 5-12 feature descriptions, each a clear sentence or phrase listing real capabilities. **`projectStructure`** `string` 2-4 sentences describing the repo layout: important directories, source vs config locations, monorepo status, and entry points. **`gettingStarted`** `string` Step-by-step instructions in 3-6 sentences: install command, environment setup, main run/build commands, and documentation links. **`notableDependencies`** `string[]` Array of 5-12 important package/library names the project uses (from package.json, requirements.txt, Cargo.toml, etc.). **`useCases`** `string[]` Array of 3-8 concrete scenarios describing when to use this project (e.g., "Building admin dashboards", "CLI tool for X"). #### Example Request [#example-request] ```bash curl "https://your-worker.workers.dev/repo?url=https://github.com/cloudflare/workers-sdk" ``` #### Example Response [#example-response] ```json { "summary": "Workers SDK is the official development toolkit for building Cloudflare Workers. It provides CLI tools, TypeScript types, and local development servers for developers building serverless applications on Cloudflare's edge network. The project includes Wrangler, the primary CLI for managing Workers, along with templates and testing utilities.", "mainTechnologies": [ "TypeScript", "Node.js", "Vitest", "esbuild", "Miniflare", "WebAssembly", "Service Workers API" ], "howItWorks": "The SDK is organized as a monorepo containing Wrangler CLI, Miniflare local simulator, and supporting packages. Wrangler provides commands for building, testing, and deploying Workers, while Miniflare simulates the Workers runtime locally. Developers write TypeScript/JavaScript, and the toolchain bundles and deploys to Cloudflare's edge. To run: install dependencies with `pnpm install`, build with `pnpm run build`, and test with `pnpm test`.", "keyFeatures": [ "Wrangler CLI for Worker development and deployment", "Local development server with hot reload", "TypeScript type definitions for Workers APIs", "Miniflare local runtime simulator", "D1 database and KV storage integration", "Tail logs and real-time debugging", "Pages deployment support" ], "projectStructure": "Monorepo with packages/ directory containing Wrangler, Miniflare, and shared libraries. Source code lives in packages/*/src/, with TypeScript configs at the root. Each package has its own package.json and build configuration.", "gettingStarted": "Install pnpm globally, then run `pnpm install` in the repo root. Build all packages with `pnpm run build`. Run tests with `pnpm test`. For Wrangler development, use `pnpm --filter wrangler dev`. See the Wrangler documentation at developers.cloudflare.com/workers/wrangler/.", "notableDependencies": [ "esbuild", "miniflare", "vitest", "unenv", "nanoid", "undici", "@cloudflare/workers-types" ], "useCases": [ "Developing Cloudflare Workers applications", "Testing Workers locally before deployment", "Managing Workers projects with CLI automation", "Building edge-first serverless applications" ] } ``` #### Error Responses [#error-responses] **`error`** `string` Human-readable error message. **`code`** `string` Machine-readable error code. **400 Bad Request** - Missing or invalid GitHub URL: ```json { "error": "Missing query parameter: url", "code": "INVALID_URL" } ``` ```json { "error": "Invalid GitHub repo URL", "code": "INVALID_URL" } ``` **502 Bad Gateway** - Failed to fetch from GitHub or AI processing error: ```json { "error": "Failed to fetch or explain repo", "code": "FETCH_OR_AI_ERROR" } ``` ## Implementation Details [#implementation-details] ### GitHub Content Fetching [#github-content-fetching] The worker uses the GitHub API to fetch repository content: ```typescript // From src/lib/github.ts export const GITHUB_API_BASE = "https://api.github.com"; export const MAX_CONTENT_CHARS = 24_000; const COMMON_HEADERS = { "User-Agent": "Cloudflare-Experiments-GithubRepoExplainer/1.0", }; ``` #### File Prioritization [#file-prioritization] The worker intelligently fetches the most important files: ```typescript const priorityNames = [ "package.json", "package-lock.json", "Cargo.toml", "pyproject.toml", "go.mod", "tsconfig.json", "vite.config.ts", "vite.config.js", "next.config.js", "next.config.mjs", ]; ``` Up to 12 files are fetched, with priority files first, then others from the root directory. ### AI Model Configuration [#ai-model-configuration] ```typescript // From src/constants/defaults.ts export const AI_MODEL = "@cf/meta/llama-3.1-8b-instruct"; export const AI_MAX_TOKENS = 2048; export const FETCH_TIMEOUT_MS = 15_000; ``` ### Request Flow [#request-flow] <Steps> <Step> ### Parse GitHub URL [#parse-github-url] Extract owner and repo name from the URL: ```typescript const parsed = parseRepoUrl(urlParam); if (!parsed) return jsonError(c, "Invalid GitHub repo URL", "INVALID_URL"); // Returns: { owner: "cloudflare", repo: "workers-sdk" } ``` </Step> <Step> ### Fetch Repository Content [#fetch-repository-content] Retrieve README and key files from GitHub API: ```typescript const content = await fetchRepoContent(parsed.owner, parsed.repo); ``` This fetches: * README (up to 24,000 characters) * Up to 12 priority configuration files * Each file truncated to 4,000 characters if needed </Step> <Step> ### Generate AI Explanation [#generate-ai-explanation] Send content to Workers AI with a structured prompt: ```typescript const result = await explainRepo(c.env, parsed.owner, parsed.repo, content); ``` The AI is instructed to return a JSON object with exactly 8 fields covering different aspects of the project. </Step> <Step> ### Parse and Validate Response [#parse-and-validate-response] Extract and validate the JSON response: ```typescript // Handles markdown code blocks, malformed JSON, and missing fields const extracted = extractJson(raw); let parsed = JSON.parse(extracted); // Falls back to jsonrepair if initial parse fails ``` </Step> </Steps> ### Core Implementation [#core-implementation] Here's the main route handler from `src/routes/repo.ts`: ```typescript app.get("/repo", async (c) => { const urlParam = c.req.query("url"); if (!urlParam?.trim()) return jsonError(c, "Missing query parameter: url", "INVALID_URL"); const parsed = parseRepoUrl(urlParam); if (!parsed) return jsonError(c, "Invalid GitHub repo URL", "INVALID_URL"); try { const content = await fetchRepoContent(parsed.owner, parsed.repo); const result = await explainRepo(c.env, parsed.owner, parsed.repo, content); return jsonSuccess(c, result); } catch (e) { const message = e instanceof Error ? e.message : "Failed to fetch or explain repo"; return jsonError(c, message, "FETCH_OR_AI_ERROR", 502); } }); ``` ### AI Prompt Structure [#ai-prompt-structure] The worker sends a detailed prompt to ensure consistent, structured responses: ```typescript const prompt = `You are a technical explainer. Analyze the following content from the GitHub repo "${owner}/${repo}" (README and key files). CRITICAL: Your entire response must be exactly one valid JSON object. Do not output any text before or after it. Do not wrap in markdown or code blocks. Do not say "Here is the JSON". Start your response with { and end with }. Use exactly these keys (all required): 1. "summary": A substantive paragraph (4-6 sentences) describing what the project is, who it's for, and what problem it solves... 2. "mainTechnologies": Array of 5-15 strings: languages, frameworks, runtimes, build tools... [... 6 more detailed field specifications ...] Content: ${context}`; ``` ## Advanced Usage [#advanced-usage] ### Adding GitHub Authentication [#adding-github-authentication] To increase rate limits to 5,000 requests/hour, add a GitHub token: 1. Create a [GitHub personal access token](https://github.com/settings/tokens) with `repo` scope 2. Add it as a Workers secret: ```bash wrangler secret put GITHUB_TOKEN ``` 3. Update `src/lib/github.ts` to include the token: ```typescript headers: { Authorization: `Bearer ${env.GITHUB_TOKEN}`, // ... other headers } ``` ### Customizing File Selection [#customizing-file-selection] Modify `priorityNames` in `src/lib/github.ts` to fetch different configuration files: ```typescript const priorityNames = [ "package.json", "requirements.txt", // Add Python projects "Dockerfile", // Add Docker configs "README.md", // ... your custom files ]; ``` ## Use Cases [#use-cases] * **Developer Onboarding** - Quickly understand new codebases before contributing * **Tech Stack Analysis** - Identify technologies and dependencies in projects * **Project Discovery** - Evaluate open-source libraries for your needs * **Documentation Generation** - Auto-generate project overviews * **Code Review Tools** - Provide context about repository structure * **IDE Extensions** - Add "explain this repo" functionality to editors ## Limitations [#limitations] * **Public repositories only**: Private repos require GitHub authentication (not implemented) * **Rate limits**: GitHub API has [rate limits](https://docs.github.com/en/rest/overview/rate-limits) (60 requests/hour unauthenticated) * **File selection**: Only fetches files from the root directory (no subdirectories) * **Content size**: README limited to 24KB, individual files to 4KB, total context to 22KB * **AI accuracy**: Responses depend on model performance and may vary * **No git history**: Only analyzes the current main branch content ## Deployment [#deployment] <Steps> <Step> ### Click the deploy button [#click-the-deploy-button] [![Deploy to Cloudflare Workers](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/shrinathsnayak/cloudflare-experiments/tree/main/apps/experiments/github-repo-explainer) </Step> <Step> ### Deploy [#deploy] Enable the **Workers AI** binding (`AI`) in your Worker settings. The deploy button configures this automatically via `wrangler.json`. Requires a Cloudflare account with Workers AI enabled. </Step> <Step> ### Test your deployment [#test-your-deployment] ```bash curl "https://your-worker.workers.dev/repo?url=https://github.com/cloudflare/workers-sdk" ``` </Step> </Steps> ## Local Development [#local-development] ```bash cd apps/experiments/github-repo-explainer npm install npm run dev ``` Test locally: ```bash curl "http://localhost:8787/repo?url=https://github.com/cloudflare/workers-sdk" ``` ## Configuration [#configuration] The Worker automatically binds to Workers AI. The `wrangler.json` configuration includes: ```json { "name": "github-repo-explainer", "main": "src/index.ts", "compatibility_date": "2024-01-01", "ai": { "binding": "AI" } } ``` No additional environment variables or secrets are required. GitHub API is used without authentication (subject to rate limits). ### Dependencies [#dependencies] ```json { "dependencies": { "hono": "^4.6.12", "jsonrepair": "^3.13.2" }, "devDependencies": { "@cloudflare/workers-types": "^4.20241127.0", "typescript": "^5.7.2", "wrangler": "^4" } } ``` The `jsonrepair` package helps handle malformed JSON responses from the AI model. ## Cloudflare Features Used [#cloudflare-features-used] * **[Workers](https://developers.cloudflare.com/workers/)** - Serverless execution environment * **[Workers AI](https://developers.cloudflare.com/workers-ai/)** - Run LLMs on the edge with the `AI` binding * **[Fetch API](https://developers.cloudflare.com/workers/runtime-apis/fetch/)** - HTTP client for GitHub API requests ## Next Steps [#next-steps] <Cards> <Card title="Workers AI Models" href="https://developers.cloudflare.com/workers-ai/models/"> Explore other models available in Workers AI </Card> <Card title="GitHub API" href="https://docs.github.com/en/rest"> Learn more about GitHub's REST API </Card> <Card title="GitHub Repository" href="https://github.com/shrinathsnayak/cloudflare-experiments/tree/main/apps/experiments/github-repo-explainer"> View the complete source code </Card> <Card title="More Experiments" href="/"> Explore other Cloudflare experiments </Card> </Cards> # HTML Rewriter (/docs/experiments/html-rewriter) Fetch remote HTML pages and analyze or transform them using the [HTMLRewriter](https://developers.cloudflare.com/workers/runtime-apis/html-rewriter/) API. Stream-parse documents at the edge to count elements or inject content without loading a full DOM parser. ## API Reference [#api-reference] ### GET /stats [#get-stats] Fetches a page and returns HTML statistics collected via HTMLRewriter handlers. <TypeTable type="{ url: { description: "Page URL to analyze (`http://` or `https://` only)", type: "string", required: true, }, }" /> #### Example Request [#example-request] ```bash curl "https://your-worker.workers.dev/stats?url=https://example.com" ``` #### Success Response [#success-response] **`url`** `string` The requested page URL **`title`** `string | null` Text content of the `<title>` element, if present **`linkCount`** `number` Number of `<a>` elements on the page **`imageCount`** `number` Number of `<img>` elements on the page **`headingCounts`** `object` Count of each heading tag (e.g. `h1`, `h2`) found on the page ```json { "url": "https://example.com", "title": "Example Domain", "linkCount": 1, "imageCount": 0, "headingCounts": { "h1": 1 } } ``` #### Error Response [#error-response] ```json { "error": "Missing or invalid query parameter: url", "code": "INVALID_URL" } ``` #### Error Codes [#error-codes] * `400` - Missing or invalid `url` (`INVALID_URL`) * `502` - Failed to fetch or analyze the page (`FETCH_ERROR`) ### GET /transform [#get-transform] Fetches a page and injects a banner `<div>` at the top of `<body>` using HTMLRewriter. <TypeTable type="{ url: { description: "Page URL to transform (`http://` or `https://` only)", type: "string", required: true, }, banner: { description: "Banner text to inject (default: `Transformed by Cloudflare HTMLRewriter at the edge`)", type: "string", }, }" /> #### Example Request [#example-request-1] ```bash curl "https://your-worker.workers.dev/transform?url=https://example.com&banner=Hello%20from%20the%20edge" ``` #### Success Response [#success-response-1] **`url`** `string` The requested page URL **`banner`** `string` The banner text that was injected **`html`** `string` The transformed HTML document ```json { "url": "https://example.com", "banner": "Transformed by Cloudflare HTMLRewriter at the edge", "html": "<html>...</html>" } ``` #### Error Response [#error-response-1] ```json { "error": "Missing or invalid query parameter: url", "code": "INVALID_URL" } ``` #### Error Codes [#error-codes-1] * `400` - Missing or invalid `url` (`INVALID_URL`) * `502` - Failed to fetch or transform the page (`FETCH_ERROR`) ## Use Cases [#use-cases] * Audit page structure (links, images, headings) without a headless browser * Inject edge banners or notices into proxied HTML responses * Learn streaming HTML transformation with HTMLRewriter handlers * Build lightweight HTML analysis tools at the edge ## Limitations [#limitations] * Static HTML fetch only; JavaScript-rendered content is not executed * HTML body size is capped before streaming through HTMLRewriter * Single URL per request; no multi-page crawling * Fetch timeout on slow origins ## Deployment [#deployment] <Steps> <Step> ### Click the deploy button [#click-the-deploy-button] [![Deploy to Cloudflare Workers](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/shrinathsnayak/cloudflare-experiments/tree/main/apps/experiments/html-rewriter) </Step> <Step> ### Deploy [#deploy] No additional configuration required. </Step> <Step> ### Test your deployment [#test-your-deployment] ```bash curl "https://your-worker.workers.dev/stats?url=https://example.com" ``` </Step> </Steps> ## Local Development [#local-development] ```bash cd apps/experiments/html-rewriter npm install npm run dev ``` Test locally: ```bash curl "http://localhost:8787/stats?url=https://example.com" curl "http://localhost:8787/transform?url=https://example.com" ``` ## Cloudflare Features Used [#cloudflare-features-used] * **[Workers](https://developers.cloudflare.com/workers/)** - Edge compute runtime * **[HTMLRewriter](https://developers.cloudflare.com/workers/runtime-apis/html-rewriter/)** - Streaming HTML parsing and transformation * **[Fetch API](https://developers.cloudflare.com/workers/runtime-apis/fetch/)** - Remote HTML retrieval # HTML to Markdown (/docs/experiments/html-to-markdown) Fetch a webpage and convert its HTML into clean Markdown for notes, LLM prompts, and content pipelines — all from Cloudflare's edge. ## Features [#features] * Converts headings, paragraphs, emphasis, links, images, lists, code, and blockquotes * Prefers `<article>` / `<main>` content when present (falls back to `<body>`) * Resolves relative link and image URLs against the page URL * Strips `script`, `style`, `svg`, and similar non-content tags * Caps HTML input at 1MB and Markdown output at 200k characters * Stateless; no bindings required ## API Reference [#api-reference] ### GET /markdown [#get-markdown] Fetches HTML and returns title plus Markdown. <TypeTable type="{ url: { description: "Page URL (must be http or https)", type: "string", required: true, }, }" /> #### Example Request [#example-request] ```bash curl "https://your-worker.workers.dev/markdown?url=https://example.com" ``` #### Success Response [#success-response] **`url`** `string` Final page URL after redirects **`title`** `string | null` Document `<title>` when present **`markdown`** `string` Converted Markdown body ```json { "url": "https://example.com/", "title": "Example Domain", "markdown": "# Example Domain\n\nThis domain is for use in illustrative examples…" } ``` #### Error Response [#error-response] ```json { "error": "Expected HTML, got content-type: application/json", "code": "NOT_HTML" } ``` #### Error Codes [#error-codes] * `400` - Missing or invalid `url` (`INVALID_URL`) * `400` - Response was not HTML (`NOT_HTML`) * `502` - Fetch failed (`FETCH_ERROR`) ## Use Cases [#use-cases] * Turn blog posts into Markdown for note-taking apps * Prepare page content for LLM prompts without raw HTML noise * Build content migration / archival pipelines on Workers * Complement readability extraction with a portable text format ## Limitations [#limitations] * Does not execute JavaScript; client-rendered content is not included * Table conversion is not fully structured (tables flatten as block text) * Complex nested layouts may produce extra blank lines * Conversion is heuristic, not a full HTML5 / CommonMark guarantee ## Deployment [#deployment] <Steps> <Step> ### Click the deploy button [#click-the-deploy-button] [![Deploy to Cloudflare Workers](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/shrinathsnayak/cloudflare-experiments/tree/main/apps/experiments/html-to-markdown) </Step> <Step> ### Deploy [#deploy] Follow the deployment wizard. No bindings or secrets required. </Step> <Step> ### Test your deployment [#test-your-deployment] ```bash curl "https://your-worker.workers.dev/markdown?url=https://example.com" ``` </Step> </Steps> ## Local Development [#local-development] ```bash cd apps/experiments/html-to-markdown npm install npm run dev ``` Test locally: ```bash curl "http://localhost:8787/markdown?url=https://www.cloudflare.com" ``` ## Cloudflare Features Used [#cloudflare-features-used] * **[Workers](https://developers.cloudflare.com/workers/)** - Edge compute runtime * **[Fetch API](https://developers.cloudflare.com/workers/runtime-apis/fetch/)** - HTML retrieval from the edge ## Next Steps [#next-steps] <Cards> <Card title="Readability Extractor" href="/docs/experiments/readability-extractor"> Extract clean article content with Browser Rendering </Card> <Card title="Website to llms.txt" href="/docs/experiments/website-to-llms-txt"> Convert pages into llms.txt for LLM consumption </Card> <Card title="JSON-LD Extractor" href="/docs/experiments/json-ld-extractor"> Extract Schema.org structured data from HTML </Card> </Cards> # Hyperdrive SQL Demo (/docs/experiments/hyperdrive-sql-demo) Connect to an existing PostgreSQL database through **Cloudflare Hyperdrive** and run safe read-only queries from the edge using Postgres.js. ## Features [#features] * Hyperdrive binding with Postgres.js (`nodejs_compat`) * `GET /status` redacted connection metadata (host/db/user) * `GET /ping` database identity + latency * `POST /query` single read-only `SELECT` (max 50 rows) * Rejects write statements and multi-statement SQL ## API Reference [#api-reference] ### GET /status [#get-status] Returns redacted Hyperdrive target metadata without exposing the password. #### Example Request [#example-request] ```bash curl "https://your-worker.workers.dev/status" ``` #### Success Response [#success-response] **`bound`** `boolean` Whether the Hyperdrive binding is present **`host`** `string` Database host (and port when present) **`database`** `string` Database name **`user`** `string` Database user ```json { "bound": true, "host": "db.example.com:5432", "database": "app", "user": "alice" } ``` ### GET /ping [#get-ping] Runs `SELECT current_database(), current_user, version()` through Hyperdrive. #### Example Request [#example-request-1] ```bash curl "https://your-worker.workers.dev/ping" ``` #### Success Response [#success-response-1] **`ok`** `boolean` Always `true` on success **`database`** `string` Current database name **`user`** `string` Current database user **`serverVersion`** `string` Postgres `version()` string **`latencyMs`** `number` Round-trip query latency in milliseconds ```json { "ok": true, "database": "app", "user": "alice", "serverVersion": "PostgreSQL 16.2 on x86_64-pc-linux-gnu", "latencyMs": 18 } ``` ### POST /query [#post-query] Runs a single read-only `SELECT` and returns up to 50 rows. <TypeTable type="{ sql: { description: "A single SELECT statement (writes and multi-statements are rejected)", type: "string", required: true, }, }" /> #### Example Request [#example-request-2] ```bash curl -X POST "https://your-worker.workers.dev/query" \ -H "content-type: application/json" \ -d '{"sql":"SELECT 1 AS ok"}' ``` #### Success Response [#success-response-2] **`sql`** `string` Validated SQL that was executed **`rowCount`** `number` Number of rows returned (capped at 50) **`rows`** `array` Result rows as JSON objects **`latencyMs`** `number` Query latency in milliseconds ```json { "sql": "SELECT 1 AS ok", "rowCount": 1, "rows": [{ "ok": 1 }], "latencyMs": 12 } ``` #### Error Response [#error-response] ```json { "error": "Invalid SQL: only a single read-only SELECT statement is allowed", "code": "INVALID_SQL" } ``` #### Error Codes [#error-codes] * `400` - Invalid or missing JSON body (`INVALID_BODY`) * `400` - SQL rejected by allowlist (`INVALID_SQL`) * `502` - Hyperdrive binding missing (`MISSING_BINDING`) * `502` - Database error (`DB_ERROR`) ## Use Cases [#use-cases] * Accelerate queries from Workers to regional Postgres * Prove Hyperdrive pooling/caching against an existing database * Build read-only edge SQL playgrounds * Teach `nodejs_compat` + Postgres.js patterns on Workers ## Limitations [#limitations] * Requires a real Hyperdrive config and reachable Postgres database * Only single `SELECT` statements are allowed * Results are capped at 50 rows * Local development needs `localConnectionString` pointing at a reachable DB ## Deployment [#deployment] <Steps> <Step> ### Create Hyperdrive [#create-hyperdrive] ```bash npx wrangler hyperdrive create hyperdrive-sql-demo \ --connection-string="postgres://USER:PASSWORD@HOST:5432/DATABASE" ``` </Step> <Step> ### Click the deploy button [#click-the-deploy-button] [![Deploy to Cloudflare Workers](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/shrinathsnayak/cloudflare-experiments/tree/main/apps/experiments/hyperdrive-sql-demo) Paste the Hyperdrive `id` into `wrangler.json` under `hyperdrive[0].id`. </Step> <Step> ### Test your deployment [#test-your-deployment] ```bash curl "https://your-worker.workers.dev/ping" ``` </Step> </Steps> ## Local Development [#local-development] ```bash cd apps/experiments/hyperdrive-sql-demo npm install npm run dev ``` Set `hyperdrive[0].localConnectionString` in `wrangler.json` to a local or remote Postgres URL, then: ```bash curl "http://localhost:8787/status" curl "http://localhost:8787/ping" curl -X POST "http://localhost:8787/query" \ -H "content-type: application/json" \ -d '{"sql":"SELECT 1 AS ok"}' ``` ## Configuration [#configuration] | Binding / setting | Purpose | | ---------------------------------------- | --------------------------------------- | | `HYPERDRIVE` | Hyperdrive binding (`connectionString`) | | `compatibility_flags: ["nodejs_compat"]` | Required for Postgres.js | | `postgres` ^3.4.5 | Postgres client dependency | ## Cloudflare Features Used [#cloudflare-features-used] * **[Workers](https://developers.cloudflare.com/workers/)** - Edge compute runtime * **[Hyperdrive](https://developers.cloudflare.com/hyperdrive/)** - Database connection pooling and caching * **[Node.js compatibility](https://developers.cloudflare.com/workers/runtime-apis/nodejs/)** - Postgres.js driver support ## Next Steps [#next-steps] <Cards> <Card title="D1 SQL Playground" href="/docs/experiments/d1-sql-playground"> Run read-only SQL against edge SQLite (D1) </Card> <Card title="Presigned R2 Upload" href="/docs/experiments/presigned-r2-upload"> Another storage-oriented edge experiment </Card> </Cards> # Image Resizer (/docs/experiments/image-resizer) Fetch and resize remote images using Cloudflare [Image Resizing](https://developers.cloudflare.com/images/transform-images/) via the `fetch` API and `cf.image` options. Returns the resized image binary with the appropriate `Content-Type` header. ## API Reference [#api-reference] ### GET /resize [#get-resize] Fetches a remote image and returns a resized version. <TypeTable type="{ url: { description: "Source image URL (`http://` or `https://` only)", type: "string", required: true, }, width: { description: "Target width in pixels (1–4096). At least one of `width` or `height` is required.", type: "number", }, height: { description: "Target height in pixels (1–4096). At least one of `width` or `height` is required.", type: "number", }, fit: { description: "Resize fit mode: `scale-down` (default), `contain`, `cover`, `crop`, or `pad`", type: "string", }, }" /> #### Example Request [#example-request] ```bash curl "https://your-worker.workers.dev/resize?url=https://example.com/image.jpg&width=800&fit=scale-down" --output resized.jpg ``` #### Success Response [#success-response] Returns the resized image as binary data with an appropriate `Content-Type` header (e.g. `image/jpeg`, `image/png`). #### Error Response [#error-response] ```json { "error": "Missing or invalid query parameter: url", "code": "INVALID_URL" } ``` #### Error Codes [#error-codes] * `400` - Missing or invalid `url` (`INVALID_URL`) * `400` - Invalid `width` value (`INVALID_WIDTH`) * `400` - Invalid `height` value (`INVALID_HEIGHT`) * `400` - Neither `width` nor `height` provided (`MISSING_DIMENSION`) * `400` - Invalid `fit` mode (`INVALID_FIT`) * `502` - Upstream fetch or resize failed (`FETCH_ERROR`) ## Use Cases [#use-cases] * Serve dynamically resized thumbnails from any remote image URL * Learn Cloudflare Image Resizing options via Workers `fetch` * Build on-the-fly image proxy endpoints at the edge * Reduce bandwidth by resizing images before delivery to clients ## Limitations [#limitations] * Remote image fetch is subject to a timeout * Resizing options are limited to the query params exposed by this experiment * Origin must allow Cloudflare to fetch the image URL * Single image per request; no batch processing ## Deployment [#deployment] <Steps> <Step> ### Click the deploy button [#click-the-deploy-button] [![Deploy to Cloudflare Workers](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/shrinathsnayak/cloudflare-experiments/tree/main/apps/experiments/image-resizer) </Step> <Step> ### Deploy [#deploy] Image Resizing must be enabled on your Cloudflare zone. On `workers.dev` URLs, resizing may not be available. </Step> <Step> ### Test your deployment [#test-your-deployment] ```bash curl "https://your-worker.workers.dev/resize?url=https://example.com/image.jpg&width=800&fit=scale-down" --output resized.jpg ``` </Step> </Steps> ## Local Development [#local-development] ```bash cd apps/experiments/image-resizer npm install npm run dev ``` Test locally: ```bash curl "http://localhost:8787/resize?url=https://example.com/image.jpg&width=800&fit=scale-down" --output resized.jpg ``` ## Cloudflare Features Used [#cloudflare-features-used] * **[Workers](https://developers.cloudflare.com/workers/)** - Edge compute runtime * **[Image Resizing](https://developers.cloudflare.com/images/transform-images/)** - `fetch` with `cf.image` transform options * **[Fetch API](https://developers.cloudflare.com/workers/runtime-apis/fetch/)** - Remote image retrieval # Is It Down? (/docs/experiments/is-it-down) Check whether any website is reachable from Cloudflare's edge, with response time, status code, and edge location information. ## API Reference [#api-reference] ### GET /check [#get-check] Checks if the specified URL is reachable and returns timing and status information. <TypeTable type="{ url: { description: "The target URL to check (must be http or https)", type: "string", required: true, }, }" /> #### Example Request [#example-request] ```bash curl "https://your-worker.workers.dev/check?url=https://www.cloudflare.com" ``` #### Success Response (Reachable) [#success-response-reachable] **`status`** `string` Reachability status: `"reachable"` or `"unreachable"` **`responseTime`** `number` Round-trip response time in milliseconds **`statusCode`** `number` HTTP status code returned by the target server **`colo`** `string` Cloudflare edge data center (IATA code) that served this check (only available when deployed) ```json { "status": "reachable", "responseTime": 87, "statusCode": 200, "colo": "LHR" } ``` #### Success Response (Unreachable) [#success-response-unreachable] **`error`** `string` Error message explaining why the site is unreachable (only present when status is `"unreachable"`) ```json { "status": "unreachable", "responseTime": 5000, "statusCode": 0, "colo": "SFO", "error": "The operation was aborted." } ``` #### Error Response [#error-response] ```json { "error": "Missing or invalid query parameter: url", "code": "INVALID_URL" } ``` #### Error Codes [#error-codes] * `400` - Missing or invalid `url` parameter (not http/https) ## Use Cases [#use-cases] * Monitor website availability from the edge * Measure response times from different geographic locations * Build uptime monitoring tools * Check if a site is down globally or just from your location ## Limitations [#limitations] * Checks from the edge colo serving the request, not from every region globally * Fetch timeout may mark slow but reachable sites as down * HTTP reachability only; no SSL certificate or DNS health checks ## Deployment [#deployment] <Steps> <Step> ### Click the deploy button [#click-the-deploy-button] [![Deploy to Cloudflare Workers](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/shrinathsnayak/cloudflare-experiments/tree/main/apps/experiments/is-it-down) </Step> <Step> ### Deploy [#deploy] Follow the deployment wizard to deploy the Worker to your Cloudflare account. No additional configuration required. </Step> <Step> ### Test your deployment [#test-your-deployment] ```bash curl "https://your-worker.workers.dev/check?url=https://example.com" ``` </Step> </Steps> ## Local Development [#local-development] ```bash cd apps/experiments/is-it-down npm install npm run dev ``` Test locally: ```bash curl "http://localhost:8787/check?url=https://www.cloudflare.com" ``` <Callout> The `colo` field will not be populated during local development. It only appears when deployed to Cloudflare Workers. </Callout> ## Cloudflare Features Used [#cloudflare-features-used] * **[Workers](https://developers.cloudflare.com/workers/)** - Edge compute runtime * **[Fetch API](https://developers.cloudflare.com/workers/runtime-apis/fetch/)** - HTTP requests with timing * **[Edge network](https://developers.cloudflare.com/workers/reference/how-workers-works/)** - Request metadata including data center location # JSON-LD Extractor (/docs/experiments/json-ld-extractor) Fetch a webpage and extract `application/ld+json` structured data blocks for Schema.org / rich-results debugging from Cloudflare's edge. ## Features [#features] * Parses all `script[type="application/ld+json"]` blocks * Collects `@type` values including nested `@graph` * Reports invalid JSON blocks without failing the whole response * Caps extraction at 25 script blocks * Stateless; no bindings required ## API Reference [#api-reference] ### GET /extract [#get-extract] Fetches HTML and returns parsed JSON-LD blocks plus discovered types. <TypeTable type="{ url: { description: "Page URL (must be http or https)", type: "string", required: true, }, }" /> #### Example Request [#example-request] ```bash curl "https://your-worker.workers.dev/extract?url=https://www.cloudflare.com" ``` #### Success Response [#success-response] **`url`** `string` Final page URL after redirects **`count`** `number` Number of JSON-LD script blocks found **`types`** `string[]` Sorted unique `@type` values across all blocks (including `@graph`) **`blocks`** `array` Per-block results with `index`, `types`, `data`, and optional `parseError` ```json { "url": "https://www.cloudflare.com/", "count": 2, "types": ["Organization", "WebSite"], "blocks": [ { "index": 0, "types": ["Organization"], "data": { "@context": "https://schema.org", "@type": "Organization", "name": "Cloudflare" } }, { "index": 1, "types": ["WebSite"], "data": { "@context": "https://schema.org", "@graph": [{ "@type": "WebSite", "name": "Cloudflare" }] } } ] } ``` #### Error Response [#error-response] ```json { "error": "Expected HTML, got content-type: application/json", "code": "NOT_HTML" } ``` #### Error Codes [#error-codes] * `400` - Missing or invalid `url` (`INVALID_URL`) * `400` - Response was not HTML (`NOT_HTML`) * `502` - Fetch failed (`FETCH_ERROR`) ## Use Cases [#use-cases] * Debug rich results / Schema.org markup * Inventory Organization, Product, Article entities on a site * Complement Open Graph previews with structured data * Build SEO auditing tools on Workers ## Limitations [#limitations] * Does not execute JavaScript-injected JSON-LD * Caps extraction at 25 script blocks * Does not validate against Schema.org vocabularies * Invalid JSON blocks are reported via `parseError` rather than failing the request ## Deployment [#deployment] <Steps> <Step> ### Click the deploy button [#click-the-deploy-button] [![Deploy to Cloudflare Workers](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/shrinathsnayak/cloudflare-experiments/tree/main/apps/experiments/json-ld-extractor) </Step> <Step> ### Deploy [#deploy] Follow the deployment wizard. No bindings or secrets required. </Step> <Step> ### Test your deployment [#test-your-deployment] ```bash curl "https://your-worker.workers.dev/extract?url=https://example.com" ``` </Step> </Steps> ## Local Development [#local-development] ```bash cd apps/experiments/json-ld-extractor npm install npm run dev ``` Test locally: ```bash curl "http://localhost:8787/extract?url=https://www.cloudflare.com" ``` ## Cloudflare Features Used [#cloudflare-features-used] * **[Workers](https://developers.cloudflare.com/workers/)** - Edge compute runtime * **[Fetch API](https://developers.cloudflare.com/workers/runtime-apis/fetch/)** - HTML retrieval from the edge ## Next Steps [#next-steps] <Cards> <Card title="Social Preview Inspector" href="/docs/experiments/social-preview-inspector"> Preview Open Graph and Twitter cards </Card> <Card title="Website Metadata Extractor" href="/docs/experiments/website-metadata-extractor"> Extract title, description, and canonical URL </Card> </Cards> # JWT Inspector (/docs/experiments/jwt-inspector) Inspect JSON Web Tokens at the edge: **decode** header and payload without verification, **verify** HS256/RS256 signatures, and **issue** test HS256 tokens for experimentation. ## Features [#features] * **POST /decode** - Parse JWT header and payload (no crypto verification) * **POST /verify** - Verify HS256 (secret) or RS256 (public key PEM) * **POST /issue** - Mint test HS256 tokens with configurable expiry * **Web Crypto** - No external JWT libraries required ## API Reference [#api-reference] ### POST /decode [#post-decode] Decode a JWT without verifying the signature. <TypeTable type="{ token: { description: "JWT string (three dot-separated segments).", type: "string", required: true, }, }" /> #### Example Request [#example-request] ```bash curl -X POST "https://your-worker.workers.dev/decode" \ -H "Content-Type: application/json" \ -d '{"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."}' ``` #### Success Response [#success-response] ```json { "header": { "alg": "HS256", "typ": "JWT" }, "payload": { "sub": "user-1", "exp": 1710000000 } } ``` #### Error Codes [#error-codes] * `400` - Invalid JSON (`INVALID_BODY`), token (`INVALID_TOKEN`), or decode error (`DECODE_ERROR`) ### POST /verify [#post-verify] Verify a JWT signature. <TypeTable type="{ token: { description: "JWT string to verify.", type: "string", required: true, }, secret: { description: "Shared secret for HS256 verification.", type: "string", required: false, }, publicKey: { description: "PEM-encoded public key for RS256 verification.", type: "string", required: false, }, }" /> Provide `secret` (HS256) or `publicKey` (RS256), not both. #### Example Request [#example-request-1] ```bash curl -X POST "https://your-worker.workers.dev/verify" \ -H "Content-Type: application/json" \ -d '{"token":"eyJ...","secret":"demo-secret-key"}' ``` #### Success Response [#success-response-1] ```json { "valid": true, "algorithm": "HS256", "payload": { "sub": "alice", "exp": 1710000000 } } ``` #### Error Codes [#error-codes-1] * `400` - Invalid body/token (`INVALID_BODY`, `INVALID_TOKEN`, `MISSING_KEY`, `VERIFY_ERROR`) ### POST /issue [#post-issue] Issue a test HS256 JWT. <TypeTable type="{ secret: { description: "Signing secret (min 8 characters).", type: "string", required: true, }, subject: { description: "JWT `sub` claim (default: test-user).", type: "string", required: false, }, expiresInSeconds: { description: "Token lifetime in seconds (default: 3600).", type: "number", required: false, }, }" /> #### Example Request [#example-request-2] ```bash curl -X POST "https://your-worker.workers.dev/issue" \ -H "Content-Type: application/json" \ -d '{"secret":"demo-secret-key","subject":"alice","expiresInSeconds":3600}' ``` #### Success Response [#success-response-2] ```json { "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "payload": { "sub": "alice", "iat": 1710000000, "exp": 1710003600, "iss": "jwt-inspector-demo" } } ``` #### Error Codes [#error-codes-2] * `400` - Invalid body or secret (`INVALID_BODY`, `INVALID_SECRET`) * `502` - Signing failed (`ISSUE_ERROR`) ## Use Cases [#use-cases] * Debug JWT payloads during API integration * Test HS256/RS256 verification logic before production auth * Generate short-lived test tokens for local development ## Limitations [#limitations] * Demo tool only; not a production identity provider * RS256 verification requires PEM public key in request body * No support for JWKS fetching or exotic algorithms ## Deployment [#deployment] <Steps> <Step> ### Click the deploy button [#click-the-deploy-button] [![Deploy to Cloudflare Workers](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/shrinathsnayak/cloudflare-experiments/tree/main/apps/experiments/jwt-inspector) </Step> <Step> ### Test your deployment [#test-your-deployment] ```bash curl -X POST "https://your-worker.workers.dev/issue" \ -H "Content-Type: application/json" \ -d '{"secret":"demo-secret-key","subject":"alice"}' ``` </Step> </Steps> ## Local Development [#local-development] ```bash cd apps/experiments/jwt-inspector npm install npm run dev ``` ## Cloudflare Features Used [#cloudflare-features-used] * **[Workers](https://developers.cloudflare.com/workers/)** - Edge compute runtime * **[Web Crypto API](https://developers.cloudflare.com/workers/runtime-apis/web-crypto/)** - HMAC and RSA signature operations # KV Notes (/docs/experiments/kv-notes) Store and retrieve small text notes using a dedicated Workers KV namespace. ## API Reference [#api-reference] ### POST /notes [#post-notes] Create or update a note. <TypeTable type="{ id: { description: "Note identifier (letters, numbers, `_`, `-`; max 64 characters)", type: "string", required: true, }, content: { description: "Note body (max 4,000 characters)", type: "string", required: true, }, }" /> #### Example Request [#example-request] ```bash curl -X POST "https://your-worker.workers.dev/notes" \ -H "Content-Type: application/json" \ -d '{"id":"todo-1","content":"Ship the experiment"}' ``` #### Success Response [#success-response] ```json { "id": "todo-1", "content": "Ship the experiment", "updatedAt": "2025-06-20T12:00:00.000Z" } ``` ### GET /notes [#get-notes] Fetch a note by id. **`id`** `string` (required) #### Example Request [#example-request-1] ```bash curl "https://your-worker.workers.dev/notes?id=todo-1" ``` ### DELETE /notes [#delete-notes] Delete a note by id. **`id`** `string` (required) #### Error Codes [#error-codes] * `400` - Invalid id or content (`INVALID_ID`, `INVALID_CONTENT`, `INVALID_BODY`) * `404` - Note not found (`NOT_FOUND`) ## Use Cases [#use-cases] * Learn KV read/write/delete patterns without D1 * Store small configuration blobs at the edge * Prototype simple key-value APIs before adding auth ## Limitations [#limitations] * Note IDs are capped at 64 characters; content at 4,000 characters * No authentication; any client can read, write, or delete by ID * KV is eventually consistent across edge locations ## Deployment [#deployment] <Steps> <Step> ### Click the deploy button [#click-the-deploy-button] [![Deploy to Cloudflare Workers](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/shrinathsnayak/cloudflare-experiments/tree/main/apps/experiments/kv-notes) </Step> <Step> ### Configure KV [#configure-kv] Create a KV namespace and bind it as `NOTES` in `wrangler.json`. </Step> </Steps> ## Local Development [#local-development] ```bash cd apps/experiments/kv-notes npm install npm run dev ``` ## Cloudflare Features Used [#cloudflare-features-used] * **[Workers](https://developers.cloudflare.com/workers/)** - Edge compute runtime * **[Workers KV](https://developers.cloudflare.com/kv/)** - Global key-value storage # Link Shortener (/docs/experiments/link-shortener) <Callout type="idea"> Create short links that redirect to long URLs. Uses D1 as the source of truth with KV as a read-through cache for fast redirects. </Callout> ## Features [#features] * **POST /shorten**: Create a short link from a long URL * **GET /:code**: Redirect to the original URL (302 redirect) * **D1 database**: Primary storage for all links * **KV cache**: Read-through cache for fast redirects * **6-character codes**: Random alphanumeric short codes (e.g., `a1B2c3`) * **Collision handling**: Automatic retry on duplicate codes ## API Reference [#api-reference] ### Shorten URL [#shorten-url] <Card title="POST /shorten"> Create a short link for a long URL. </Card> #### Request Body [#request-body] **`param`** `string` (required) The long URL to shorten. Must start with `http://` or `https://`. #### Response [#response] **`code`** `string` The generated short code (6 alphanumeric characters) **`url`** `string` The original long URL #### Example [#example] ```bash curl -X POST https://your-worker.workers.dev/shorten \ -H "Content-Type: application/json" \ -d '{"url": "https://www.cloudflare.com/products/workers/"}' ``` ```json Response (201 Created) { "code": "a1B2c3", "url": "https://www.cloudflare.com/products/workers/" } ``` ```json Error: Invalid URL (400) { "error": "Missing or invalid body field: url (http or https only)", "code": "INVALID_URL" } ``` ```json Error: Invalid JSON (400) { "error": "Invalid or missing JSON body", "code": "INVALID_BODY" } ``` #### Usage [#usage] After creating a short link, redirect by visiting: ``` https://your-worker.workers.dev/a1B2c3 ``` *** ### Redirect [#redirect] <Card title="GET /:code"> Redirect to the original URL associated with the short code. </Card> #### Path Parameters [#path-parameters] **`param`** `string` (required) The short code (6 alphanumeric characters) #### Response [#response-1] * **302 Found**: Redirects to the original URL * **404 Not Found**: If the code doesn't exist #### Example [#example-1] ```bash # Follow redirects curl -L https://your-worker.workers.dev/a1B2c3 # Show redirect without following (see Location header) curl -I https://your-worker.workers.dev/a1B2c3 ``` ```http Success (302 Found) HTTP/1.1 302 Found Location: https://www.cloudflare.com/products/workers/ ``` ```json Error: Not found (404) { "error": "Short link not found", "code": "NOT_FOUND" } ``` ```json Error: Invalid code (404) { "error": "Invalid short code", "code": "INVALID_CODE" } ``` ## Architecture [#architecture] 1. **Shorten**: Insert into D1, then cache in KV 2. **Redirect**: Read from KV first; on cache miss, read from D1 and populate cache 3. **Source of truth**: D1 database (`links` table) ## Setup [#setup] <Steps> <Step> ### Install dependencies [#install-dependencies] ```bash cd apps/experiments/link-shortener npm install ``` </Step> <Step> ### Create D1 database [#create-d1-database] ```bash npx wrangler d1 create link-shortener-db ``` This returns output like: ``` database_id = "a1b2c3d4-1234-5678-abcd-123456789abc" ``` Copy the `database_id` value. </Step> <Step> ### Create KV namespace [#create-kv-namespace] ```bash npx wrangler kv namespace create LINKS_CACHE ``` This returns output like: ``` id = "0123456789abcdef0123456789abcdef" ``` Copy the `id` value. <Callout type="idea"> For local development, create a preview namespace: `npx wrangler kv namespace create LINKS_CACHE --preview` </Callout> </Step> <Step> ### Update wrangler.json [#update-wranglerjson] Replace the placeholder IDs with your actual values: ```json { "d1_databases": [ { "binding": "DB", "database_name": "link-shortener-db", "database_id": "a1b2c3d4-1234-5678-abcd-123456789abc" } ], "kv_namespaces": [ { "binding": "LINKS_CACHE", "id": "0123456789abcdef0123456789abcdef" } ] } ``` </Step> <Step> ### Run migrations [#run-migrations] Apply the database schema: **Local database** (for development): ```bash npm run db:migrate:local ``` **Remote database** (for production): ```bash npm run db:migrate ``` This creates the `links` table: ```sql CREATE TABLE links ( code TEXT PRIMARY KEY, url TEXT NOT NULL, created_at INTEGER NOT NULL DEFAULT (unixepoch()) ); ``` </Step> <Step> ### Run locally [#run-locally] ```bash npm run dev ``` Your Worker will be available at `http://localhost:8787` </Step> </Steps> ## Database Schema [#database-schema] ### links table [#links-table] **`param`** `TEXT` (required) Short code (PRIMARY KEY) **`param`** `TEXT` (required) Original long URL **`param`** `INTEGER` Unix timestamp (auto-generated) #### Schema SQL [#schema-sql] ```sql CREATE TABLE IF NOT EXISTS links ( code TEXT PRIMARY KEY, url TEXT NOT NULL, created_at INTEGER NOT NULL DEFAULT (unixepoch()) ); ``` ## How It Works [#how-it-works] ### Shortening flow [#shortening-flow] 1. Client sends `POST /shorten` with `{"url": "https://..."}` 2. Worker validates URL (must be http/https) 3. Generate random 6-character code from `[a-zA-Z0-9]` 4. Insert into D1: `INSERT INTO links (code, url) VALUES (?, ?)` 5. If UNIQUE constraint fails (collision), retry with new code (max 5 attempts) 6. Cache in KV: `LINKS_CACHE.put("link:code", url)` 7. Return `{"code": "...", "url": "..."}` with 201 status ### Redirect flow [#redirect-flow] 1. Client requests `GET /:code` 2. Validate code format (alphanumeric only) 3. Check KV cache: `LINKS_CACHE.get("link:code")` 4. **Cache hit**: Return 302 redirect immediately 5. **Cache miss**: Query D1: `SELECT url FROM links WHERE code = ?` 6. If found: cache result in KV, then return 302 redirect 7. If not found: return 404 error ### Code generation [#code-generation] Codes are generated using cryptographically random bytes: ```typescript const CODE_LENGTH = 6; const CODE_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; function generateCode(): string { let code = ""; const bytes = new Uint8Array(CODE_LENGTH); crypto.getRandomValues(bytes); for (let i = 0; i < CODE_LENGTH; i++) { code += CODE_CHARS[bytes[i] % CODE_CHARS.length]; } return code; } ``` Total possible codes: 62^6 = 56.8 billion ## Testing [#testing] ### Local testing [#local-testing] ```bash # Start dev server npm run dev # Shorten a URL curl -X POST http://localhost:8787/shorten \ -H "Content-Type: application/json" \ -d '{"url": "https://example.com"}' # Output: {"code":"xY3pQz","url":"https://example.com"} # Test redirect curl -I http://localhost:8787/xY3pQz # Output: Location: https://example.com ``` ### Query the database [#query-the-database] ```bash # Local database npx wrangler d1 execute link-shortener-db --local --command "SELECT * FROM links" # Remote database npx wrangler d1 execute link-shortener-db --command "SELECT * FROM links" ``` ### Inspect KV cache [#inspect-kv-cache] ```bash # List all keys npx wrangler kv key list --namespace-id=YOUR_KV_ID # Get a specific cached link npx wrangler kv key get "link:xY3pQz" --namespace-id=YOUR_KV_ID ``` ## Monitoring [#monitoring] ### View logs [#view-logs] ```bash # Tail production logs npx wrangler tail # Filter for errors npx wrangler tail --status error ``` ### Analytics [#analytics] View request metrics in the [Cloudflare dashboard](https://dash.cloudflare.com/): * **Workers & Pages** → your Worker → **Metrics** * See request volume, error rate, and response times ## Error Responses [#error-responses] All errors return JSON: ```json { "error": "Human-readable error message", "code": "ERROR_CODE" } ``` **`error`** `string` Description of what went wrong **`code`** `string` Error code: `INVALID_BODY`, `INVALID_URL`, `NOT_FOUND`, `INVALID_CODE`, `INTERNAL_ERROR` ## Source Code [#source-code] View the complete source code on GitHub: [link-shortener](https://github.com/shrinathsnayak/cloudflare-experiments/tree/main/apps/experiments/link-shortener) ## Use Cases [#use-cases] ### Marketing campaigns [#marketing-campaigns] Create memorable short links for campaigns: ```bash curl -X POST https://short.example.com/shorten \ -d '{"url": "https://example.com/summer-sale?utm_campaign=twitter"}' # Share: https://short.example.com/a1B2c3 ``` ### API URL shortening [#api-url-shortening] Integrate into your app: ```javascript const response = await fetch("https://your-worker.workers.dev/shorten", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ url: longUrl }), }); const { code, url } = await response.json(); const shortUrl = `https://your-worker.workers.dev/${code}`; ``` ### QR code generation [#qr-code-generation] Shorter URLs = simpler QR codes: ```bash # Create short link curl -X POST https://short.example.com/shorten \ -d '{"url": "https://example.com/very/long/path/to/product"}' # Generate QR code for: https://short.example.com/x7Y2kL ``` ## Limitations [#limitations] * No expiration: Links never expire (implement TTL if needed) * No analytics: No tracking of clicks or usage (add D1 table if needed) * No custom codes: Codes are always randomly generated * No link updates: Cannot change the URL for an existing code * No deletion: Cannot delete links after creation (add DELETE endpoint if needed) ## Deployment [#deployment] [![Deploy to Cloudflare Workers](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/shrinathsnayak/cloudflare-experiments/tree/main/apps/experiments/link-shortener) <Steps> <Step> ### Deploy Worker [#deploy-worker] Click the deploy button above or run: ```bash npm run deploy ``` </Step> <Step> ### Create production resources [#create-production-resources] If not already created: ```bash # Create D1 database npx wrangler d1 create link-shortener-db # Create KV namespace npx wrangler kv namespace create LINKS_CACHE ``` Update `wrangler.json` with the returned IDs. </Step> <Step> ### Run migrations [#run-migrations-1] Apply the schema to your production database: ```bash npm run db:migrate ``` </Step> <Step> ### Test production [#test-production] ```bash curl -X POST https://your-worker.workers.dev/shorten \ -H "Content-Type: application/json" \ -d '{"url": "https://www.cloudflare.com"}' ``` </Step> </Steps> ## Configuration [#configuration] ### Bindings [#bindings] **`param`** `D1Database` D1 database binding (name: `link-shortener-db`) **`param`** `KVNamespace` KV namespace binding for caching redirects ## Cloudflare Features Used [#cloudflare-features-used] * **[Workers](https://developers.cloudflare.com/workers/)** - Serverless compute at the edge * **[D1](https://developers.cloudflare.com/d1/)** - SQLite database at the edge (primary storage) * **[Workers KV](https://developers.cloudflare.com/kv/)** - Key-value store (read-through cache) * **[Hono](https://hono.dev/)** - Lightweight web framework # Live Cursor Tracker (/docs/experiments/live-cursor-tracker) Broadcast cursor positions to every client in a shared room using a **Durable Object** and the **WebSocket Hibernation API**. Open multiple tabs to `/` and move your mouse to see colored cursors sync in real time. ## Features [#features] * **GET /** - Demo page with canvas and WebSocket client * **GET /ws/:room** - WebSocket upgrade; one DO instance per room name * **Hibernation API** - `acceptWebSocket`, `webSocketMessage`, `webSocketClose` * **Colored cursors** - Each client gets a stable color and guest label ## API Reference [#api-reference] ### GET / [#get-] Returns an HTML demo page with inline JavaScript that connects to `/ws/:room`. Open `http://localhost:8787/` in two browser tabs to test locally. ### GET /ws/:room [#get-wsroom] Upgrades to WebSocket and joins the room's Durable Object. **`room`** `string` (path, required) Room name: letters, numbers, `_`, or `-` (max 64 characters). #### Client messages (JSON) [#client-messages-json] Send cursor position updates: ```json { "x": 120, "y": 340 } ``` #### Server messages (JSON) [#server-messages-json] ```json { "type": "join", "id": "uuid", "color": "#3b82f6", "name": "Guest-a1b2" } { "type": "cursor", "id": "uuid", "x": 120, "y": 340, "color": "#3b82f6", "name": "Guest-a1b2" } { "type": "leave", "id": "uuid", "color": "#3b82f6", "name": "Guest-a1b2" } ``` #### Error Codes [#error-codes] * `400` - Invalid room name (`INVALID_ROOM`) * `426` - Missing WebSocket upgrade header ## Use Cases [#use-cases] * Learn Durable Object WebSocket hibernation patterns * Prototype collaborative UI features (cursors, presence, cursors-on-canvas) * Demo real-time broadcast without an external pub/sub service ## Limitations [#limitations] * No authentication; any client can join any room name * Cursor state is ephemeral (not persisted to storage) * Demo page only; production apps would use a custom frontend ## Deployment [#deployment] <Steps> <Step> ### Click the deploy button [#click-the-deploy-button] [![Deploy to Cloudflare Workers](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/shrinathsnayak/cloudflare-experiments/tree/main/apps/experiments/live-cursor-tracker) </Step> <Step> ### Deploy [#deploy] Durable Object migration is included in `wrangler.json`. No additional bindings required. </Step> </Steps> ## Local Development [#local-development] ```bash cd apps/experiments/live-cursor-tracker npm install npm run dev ``` Open `http://localhost:8787/` in two tabs. ## Configuration [#configuration] | Binding | Purpose | | ------- | --------------------------------------------- | | `ROOMS` | Durable Object namespace (`CursorRoom` class) | ## Cloudflare Features Used [#cloudflare-features-used] * **[Workers](https://developers.cloudflare.com/workers/)** - Edge compute runtime * **[Durable Objects](https://developers.cloudflare.com/durable-objects/)** - Room-scoped WebSocket coordination * **WebSocket Hibernation API** - Efficient long-lived connections # Multi-PoP Latency Map (/docs/experiments/multi-pop-latency-map) Fetch a target URL from the edge and report response time plus the **Cloudflare colo** (`cf.colo`) that handled the Worker invocation. Documents honestly that each request runs in a **single PoP** - not a global multi-region sweep. ## Features [#features] * **GET /latency?url=** - Response time, status code, and edge location * **Colo metadata** - `colo`, `city`, `country` from `request.cf` * **Transparent limitations** - Response includes `limitation` and `tip` fields * **Cache bypass** - Uses `cf: { cacheTtl: 0 }` on the outbound fetch ## API Reference [#api-reference] ### GET /latency [#get-latency] Measure fetch latency from the current edge location. <TypeTable type="{ url: { description: "Target URL (http or https only).", type: "string", required: true, }, }" /> #### Example Request [#example-request] ```bash curl "https://your-worker.workers.dev/latency?url=https://www.cloudflare.com" ``` #### Success Response [#success-response] ```json { "url": "https://www.cloudflare.com/", "responseTimeMs": 87, "statusCode": 200, "colo": "SFO", "city": "San Francisco", "country": "US", "timestamp": "2025-06-20T12:00:00.000Z", "limitation": "Each Worker invocation runs in a single Cloudflare PoP...", "tip": "For multi-region comparison, call this endpoint repeatedly from different client locations..." } ``` #### Error Codes [#error-codes] * `400` - Invalid URL (`INVALID_URL`) * `502` - Fetch failed (`FETCH_ERROR`) <Callout type="warning"> This endpoint reports latency from **one PoP per request**. It does not perform a simultaneous multi-region ping. Call repeatedly from different geographic clients to compare colos. </Callout> ## Use Cases [#use-cases] * Show students how `request.cf.colo` identifies the serving edge * Compare latency when calling the worker from different client locations * Learn honest edge networking constraints in Workers ## Limitations [#limitations] * Single PoP per invocation; no built-in multi-region orchestration * `colo` reflects where the Worker ran, not every hop to the origin * Outbound fetch timing includes DNS + TLS + TTFB for the target URL ## Deployment [#deployment] <Steps> <Step> ### Click the deploy button [#click-the-deploy-button] [![Deploy to Cloudflare Workers](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/shrinathsnayak/cloudflare-experiments/tree/main/apps/experiments/multi-pop-latency-map) </Step> <Step> ### Test your deployment [#test-your-deployment] ```bash curl "https://your-worker.workers.dev/latency?url=https://example.com" ``` </Step> </Steps> ## Local Development [#local-development] ```bash cd apps/experiments/multi-pop-latency-map npm install npm run dev ``` <Callout> `colo` and geolocation fields are populated when deployed to Cloudflare's edge. Local `wrangler dev` may return null for some `cf` properties. </Callout> ## Cloudflare Features Used [#cloudflare-features-used] * **[Workers](https://developers.cloudflare.com/workers/)** - Edge compute runtime * **`request.cf`** - Colo and geolocation metadata * **Fetch API** - Outbound HTTP with cache controls # Page Metrics (/docs/experiments/page-metrics) Navigate to any URL in a headless browser and return Puppeteer page load metrics as JSON. ## API Reference [#api-reference] ### GET /metrics [#get-metrics] Loads the page and returns metrics from `page.metrics()`. <TypeTable type="{ url: { description: "The website URL to measure (must be http or https)", type: "string", required: true, }, }" /> #### Example Request [#example-request] ```bash curl "https://your-worker.workers.dev/metrics?url=https://www.cloudflare.com" ``` #### Success Response [#success-response] **`url`** `string` The normalized URL that was measured **`metrics`** `object` Puppeteer metrics such as `Nodes`, `LayoutCount`, `ScriptDuration`, and `JSHeapUsedSize` ```json { "url": "https://www.cloudflare.com/", "metrics": { "Timestamp": 12345.678, "Documents": 1, "Nodes": 842, "LayoutCount": 12, "ScriptDuration": 0.234, "JSHeapUsedSize": 1048576 } } ``` #### Error Response [#error-response] ```json { "error": "Missing or invalid query parameter: url", "code": "INVALID_URL" } ``` #### Error Codes [#error-codes] * `400` - Missing or invalid `url` parameter * `502` - Navigation or metrics collection failed (`METRICS_ERROR`) ## Use Cases [#use-cases] * Measure DOM complexity and script cost after full page load * Compare client-side rendering overhead across sites * Debug performance issues that only appear in a real browser * Learn Puppeteer metrics collection on Workers ## Limitations [#limitations] * Requires Browser Rendering enabled on your Cloudflare account * Local development needs `npx wrangler dev --remote` * Metrics reflect one page load at a fixed 1280×800 viewport * Navigation timeout may fail on heavy single-page applications ## Deployment [#deployment] <Steps> <Step> ### Click the deploy button [#click-the-deploy-button] [![Deploy to Cloudflare Workers](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/shrinathsnayak/cloudflare-experiments/tree/main/apps/experiments/page-metrics) </Step> <Step> ### Enable Browser Rendering [#enable-browser-rendering] Browser Rendering must be enabled on your account. </Step> <Step> ### Test your deployment [#test-your-deployment] ```bash curl "https://your-worker.workers.dev/metrics?url=https://www.cloudflare.com" ``` </Step> </Steps> ## Local Development [#local-development] ```bash cd apps/experiments/page-metrics npm install npm run dev ``` Test locally: ```bash curl "http://localhost:8787/metrics?url=https://www.cloudflare.com" ``` ## Cloudflare Features Used [#cloudflare-features-used] * **[Workers](https://developers.cloudflare.com/workers/)** - Edge compute runtime * **[Browser Rendering](https://developers.cloudflare.com/browser-rendering/)** - Headless Chromium via `@cloudflare/puppeteer` * **[Puppeteer `page.metrics()`](https://developers.cloudflare.com/browser-rendering/platform/puppeteer/)** - Page load and memory metrics # PDF API (/docs/experiments/pdf-api) Generate a PDF from any URL using Browser Rendering and Puppeteer's `page.pdf()` API. ## API Reference [#api-reference] ### GET /pdf [#get-pdf] Renders the specified URL in a headless browser and returns a PDF document. <TypeTable type="{ url: { description: "The website URL to render (must be http or https)", type: "string", required: true, }, }" /> #### Example Request [#example-request] ```bash curl "https://your-worker.workers.dev/pdf?url=https://www.cloudflare.com" \ --output page.pdf ``` #### Success Response [#success-response] Returns a PDF file with the following headers: ```http Content-Type: application/pdf Cache-Control: public, max-age=60 ``` #### Error Response [#error-response] ```json { "error": "Missing or invalid query parameter: url", "code": "INVALID_URL" } ``` #### Error Codes [#error-codes] * `400` - Missing or invalid `url` parameter (not http/https) * `502` - PDF generation failed (`PDF_ERROR`) ## Use Cases [#use-cases] * Convert web pages to printable PDF documents * Archive rendered page content with styles * Generate invoices or reports from HTML pages * Learn Browser Rendering PDF generation with Workers bindings ## Limitations [#limitations] * Requires Browser Rendering enabled on your Cloudflare account * Local development needs `npx wrangler dev --remote` * Navigation timeout; complex or infinite-scroll pages may fail * Fixed viewport; no PDF margin, format, or page-size options ## Deployment [#deployment] <Steps> <Step> ### Click the deploy button [#click-the-deploy-button] [![Deploy to Cloudflare Workers](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/shrinathsnayak/cloudflare-experiments/tree/main/apps/experiments/pdf-api) </Step> <Step> ### Enable Browser Rendering [#enable-browser-rendering] This experiment requires Browser Rendering on your Cloudflare account. See the [Browser Rendering docs](https://developers.cloudflare.com/browser-rendering/). </Step> <Step> ### Test your deployment [#test-your-deployment] ```bash curl "https://your-worker.workers.dev/pdf?url=https://www.cloudflare.com" \ --output page.pdf ``` </Step> </Steps> ## Local Development [#local-development] ```bash cd apps/experiments/pdf-api npm install npm run dev ``` <Callout> `npm run dev` uses `wrangler dev --remote` so a real browser instance is available. Local dev without `--remote` will not work. </Callout> Test locally: ```bash curl "http://localhost:8787/pdf?url=https://www.cloudflare.com" --output test.pdf ``` ## Cloudflare Features Used [#cloudflare-features-used] * **[Workers](https://developers.cloudflare.com/workers/)** - Edge compute runtime * **[Browser Rendering](https://developers.cloudflare.com/browser-rendering/)** - Headless Chromium via `@cloudflare/puppeteer` * **[Puppeteer `page.pdf()`](https://developers.cloudflare.com/browser-rendering/platform/puppeteer/)** - PDF generation from rendered pages # Presigned R2 Upload (/docs/experiments/presigned-r2-upload) Generate **presigned PUT URLs** so browsers upload files directly to **R2** without routing file bytes through the Worker. Includes a demo HTML form at `GET /`. ## Features [#features] * **POST /presign** - Returns a time-limited upload URL * **Direct browser upload** - Client PUTs to R2 using the signed URL * **Content-type validation** - Allowed types enforced at presign time * **Demo page** - `GET /` with file picker and upload flow ## API Reference [#api-reference] ### POST /presign [#post-presign] Generate a presigned PUT URL for R2. <TypeTable type="{ filename: { description: "Filename (alphanumeric, dot, dash, underscore; max 128 chars, no path segments).", type: "string", required: true, }, contentType: { description: "MIME type. Allowed: image/png, image/jpeg, image/webp, text/plain, application/pdf.", type: "string", required: true, }, }" /> #### Example Request [#example-request] ```bash curl -X POST "https://your-worker.workers.dev/presign" \ -H "Content-Type: application/json" \ -d '{"filename":"photo.png","contentType":"image/png"}' ``` #### Success Response [#success-response] ```json { "uploadUrl": "https://account.r2.cloudflarestorage.com/bucket/uploads/uuid-photo.png?X-Amz-...", "key": "uploads/550e8400-e29b-41d4-a716-446655440000-photo.png", "contentType": "image/png", "maxBytes": 5242880, "expiresInSeconds": 900 } ``` Upload the file with a matching `Content-Type`: ```bash curl -X PUT "$uploadUrl" -H "Content-Type: image/png" --data-binary @photo.png ``` #### Error Codes [#error-codes] * `400` - Invalid JSON (`INVALID_BODY`), filename (`INVALID_FILENAME`), or content type (`INVALID_CONTENT_TYPE`) * `502` - Presign failed, often missing R2 credentials (`PRESIGN_ERROR`) ### GET / [#get-] Returns an HTML demo page with presign + direct PUT upload flow. ## Use Cases [#use-cases] * Offload large file uploads from Workers to R2 * Learn aws4fetch presigned URL generation at the edge * Prototype user-generated content uploads with size/type constraints ## Limitations [#limitations] * Requires R2 API credentials as Worker secrets (`R2_ACCESS_KEY_ID`, `R2_SECRET_ACCESS_KEY`) * Browser uploads need R2 CORS configured for your origin * Signed `Content-Type` must match the client PUT header exactly * Documented max size 5 MB (enforced at presign documentation level) ## Deployment [#deployment] <Steps> <Step> ### Click the deploy button [#click-the-deploy-button] [![Deploy to Cloudflare Workers](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/shrinathsnayak/cloudflare-experiments/tree/main/apps/experiments/presigned-r2-upload) </Step> <Step> ### Configure R2 and secrets [#configure-r2-and-secrets] ```bash wrangler secret put R2_ACCESS_KEY_ID wrangler secret put R2_SECRET_ACCESS_KEY ``` Set `R2_ACCOUNT_ID` in `wrangler.json` vars. Configure R2 CORS for browser PUT uploads. </Step> <Step> ### Test presign [#test-presign] ```bash curl -X POST "https://your-worker.workers.dev/presign" \ -H "Content-Type: application/json" \ -d '{"filename":"test.txt","contentType":"text/plain"}' ``` </Step> </Steps> ## Local Development [#local-development] ```bash cd apps/experiments/presigned-r2-upload npm install npm run dev ``` Open `http://localhost:8787/` for the demo upload form. ## Configuration [#configuration] | Setting | Purpose | | ---------------------- | -------------------------------- | | `UPLOADS` | R2 bucket binding | | `R2_ACCOUNT_ID` | Account ID for signing (var) | | `R2_ACCESS_KEY_ID` | R2 API token access key (secret) | | `R2_SECRET_ACCESS_KEY` | R2 API token secret (secret) | ## Cloudflare Features Used [#cloudflare-features-used] * **[Workers](https://developers.cloudflare.com/workers/)** - Edge compute runtime * **[R2](https://developers.cloudflare.com/r2/)** - Object storage with presigned uploads # Queue Job Visualizer (/docs/experiments/queue-job-visualizer) Enqueue simulated jobs over HTTP, process them asynchronously with a **Queues consumer**, and track status transitions in **KV** (`queued` → `processing` → `done` / `failed`). The consumer simulates \~35% failure rate and retries via `message.retry()`. ## Features [#features] * **POST /jobs** - Enqueue a resize or fetch job * **GET /jobs/:id** - Poll job status from KV * **Simulated failures** - Consumer retries up to 3 attempts before marking failed * **KV state machine** - Full transition history on each job record ## API Reference [#api-reference] ### POST /jobs [#post-jobs] Enqueue a new job. <TypeTable type="{ type: { description: "Job type: `resize` or `fetch`.", type: "string", required: true, }, target: { description: "Job target string (e.g. URL or filename, max 500 chars).", type: "string", required: true, }, }" /> #### Example Request [#example-request] ```bash curl -X POST "https://your-worker.workers.dev/jobs" \ -H "Content-Type: application/json" \ -d '{"type":"fetch","target":"https://example.com/image.png"}' ``` #### Success Response (201) [#success-response-201] ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "type": "fetch", "target": "https://example.com/image.png", "status": "queued", "attempts": 0, "enqueuedAt": "2025-06-20T12:00:00.000Z", "updatedAt": "2025-06-20T12:00:00.000Z" } ``` #### Error Codes [#error-codes] * `400` - Invalid JSON (`INVALID_BODY`), type (`INVALID_TYPE`), or target (`INVALID_TARGET`) ### GET /jobs/:id [#get-jobsid] Returns the current job record from KV. #### Example Request [#example-request-1] ```bash curl "https://your-worker.workers.dev/jobs/550e8400-e29b-41d4-a716-446655440000" ``` #### Success Response [#success-response] ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "type": "fetch", "target": "https://example.com/image.png", "status": "done", "attempts": 1, "result": "Fetched https://example.com/image.png (simulated)", "enqueuedAt": "2025-06-20T12:00:00.000Z", "updatedAt": "2025-06-20T12:01:00.000Z" } ``` #### Error Codes [#error-codes-1] * `404` - Job not found (`NOT_FOUND`) <Callout> Status may stay `queued` or `processing` briefly until the queue consumer runs. Poll until `done` or `failed`. </Callout> ## Use Cases [#use-cases] * Learn Queues producer (`send`) and consumer (`queue()` handler) setup * Visualize async job lifecycles with KV-backed status * Demonstrate retry semantics with `message.retry()` and `message.ack()` ## Limitations [#limitations] * Jobs are simulated; no actual image resize or fetch occurs * Failure rate is deterministic per job id + attempt (demo only) * Requires KV namespace and Queue configured in `wrangler.json` ## Deployment [#deployment] <Steps> <Step> ### Click the deploy button [#click-the-deploy-button] [![Deploy to Cloudflare Workers](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/shrinathsnayak/cloudflare-experiments/tree/main/apps/experiments/queue-job-visualizer) </Step> <Step> ### Configure KV and Queue [#configure-kv-and-queue] Create a KV namespace bound as `JOBS` and a queue named `queue-job-visualizer`. </Step> <Step> ### Test your deployment [#test-your-deployment] ```bash curl -X POST "https://your-worker.workers.dev/jobs" \ -H "Content-Type: application/json" \ -d '{"type":"resize","target":"photo.png"}' ``` </Step> </Steps> ## Local Development [#local-development] ```bash cd apps/experiments/queue-job-visualizer npm install npm run dev ``` ## Configuration [#configuration] | Binding | Purpose | | ------------ | ----------------------------------- | | `JOBS_QUEUE` | Cloudflare Queue producer/consumer | | `JOBS` | KV namespace for job status records | ## Cloudflare Features Used [#cloudflare-features-used] * **[Workers](https://developers.cloudflare.com/workers/)** - Edge compute runtime * **[Queues](https://developers.cloudflare.com/queues/)** - Async job processing * **[Workers KV](https://developers.cloudflare.com/kv/)** - Job status storage # R2 Storage API (/docs/experiments/r2-storage) <Callout type="idea"> Upload images and files to R2 with configurable private and public access. Objects in the private bucket are only accessible through the Worker, while public bucket objects get a direct URL. </Callout> ## Features [#features] * **Private bucket**: Objects only accessible via Worker API (`GET /object?key=...`) * **Public bucket**: Objects get a public URL (e.g. `https://pub-xxx.r2.dev/key`) when uploaded with `public=true` * **List operations** with pagination, prefix filtering, and directory-style delimiters * **Custom metadata** support via `X-Custom-Metadata-*` headers * **Content-Type** handling for serving images and other media ## API Reference [#api-reference] ### List objects [#list-objects] <Card title="GET /list"> List objects from a bucket with optional filtering and pagination. </Card> #### Query Parameters [#query-parameters] **`public`** `boolean` Set to `true` or `1` to list from the public bucket. Omit for private bucket. **`prefix`** `string` Filter objects by key prefix (e.g., `images/` to list only keys starting with "images/") **`limit`** `number` Maximum number of keys to return (1-1000) **`cursor`** `string` Pagination cursor from a previous response (when `truncated: true`) **`delimiter`** `string` Delimiter for directory-style listing (e.g., `/` to group by directory) #### Response [#response] **`objects`** `array` Array of object metadata <Accordions> <Accordion title="object properties"> **`key`** `string` Object key **`size`** `number` Size in bytes **`etag`** `string` Entity tag for versioning **`uploaded`** `string` ISO 8601 timestamp **`customMetadata`** `object` Custom metadata if present </Accordion> </Accordions> **`truncated`** `boolean` Whether there are more results available **`cursor`** `string` Cursor for next page (only present when `truncated: true`) #### Example [#example] ```bash # List all objects from private bucket curl "https://your-worker.workers.dev/list" # List public objects with prefix curl "https://your-worker.workers.dev/list?public=true&prefix=images/" # Paginated listing curl "https://your-worker.workers.dev/list?limit=10&cursor=eyJrZXkiOi4uLn0" ``` <Tabs items="['Response']"> <Tab value="Response"> ```json { "objects": [ { "key": "images/photo.png", "size": 102400, "etag": "abc123def456", "uploaded": "2025-03-10T12:34:56.789Z" } ], "truncated": false } ``` </Tab> </Tabs> *** ### Get object [#get-object] <Card title="GET /object"> Download an object from R2. Returns the raw file with appropriate Content-Type. </Card> #### Query Parameters [#query-parameters-1] **`key`** `string` (required) Object key to retrieve **`public`** `boolean` Set to `true` or `1` to get from the public bucket #### Headers [#headers] **`param`** `string` Optional byte range for partial content (e.g., `bytes=0-1023`) #### Response [#response-1] Returns the raw object body with headers: * `Content-Type`: From object's HTTP metadata * `ETag`: Object version identifier * `Content-Length`: Size in bytes #### Example [#example-1] ```bash # Download private object curl "https://your-worker.workers.dev/object?key=private/document.pdf" -o document.pdf # Download public object curl "https://your-worker.workers.dev/object?key=images/photo.png&public=true" -o photo.png # Get partial content curl -H "Range: bytes=0-1023" "https://your-worker.workers.dev/object?key=video.mp4" ``` *** ### Get object metadata [#get-object-metadata] <Card title="HEAD /object"> Get object metadata without downloading the body. </Card> #### Query Parameters [#query-parameters-2] **`key`** `string` (required) Object key to check **`public`** `boolean` Set to `true` or `1` to check the public bucket #### Response [#response-2] **`key`** `string` Object key **`size`** `number` Size in bytes **`etag`** `string` Entity tag **`uploaded`** `string` ISO 8601 timestamp **`customMetadata`** `object` Custom metadata if present **`httpMetadata`** `object` HTTP metadata including contentType #### Example [#example-2] ```bash curl -X HEAD "https://your-worker.workers.dev/object?key=photo.png&public=true" ``` <Tabs items="['Response']"> <Tab value="Response"> ```json { "key": "photo.png", "size": 102400, "etag": "abc123def456", "uploaded": "2025-03-10T12:34:56.789Z", "httpMetadata": { "contentType": "image/png" } } ``` </Tab> </Tabs> *** ### Upload object [#upload-object] <Card title="PUT /object"> Upload an object to R2. Send raw bytes in the request body. </Card> #### Query Parameters [#query-parameters-3] **`key`** `string` (required) Object key (path) for the upload **`public`** `boolean` Set to `true` or `1` to upload to the public bucket and get a public URL #### Headers [#headers-1] **`param`** `string` MIME type of the file (e.g., `image/png`, `application/pdf`) **`param`** `string` Custom metadata headers (e.g., `X-Custom-Metadata-Author: John`) #### Request Body [#request-body] Raw bytes of the file to upload. #### Response [#response-3] **`key`** `string` Object key that was uploaded **`uploaded`** `boolean` Always `true` on success **`url`** `string` Public URL (only present for public bucket uploads when `PUBLIC_BUCKET_URL` is configured) #### Example [#example-3] ```bash # Upload to private bucket curl -X PUT "https://your-worker.workers.dev/object?key=documents/report.pdf" \ -H "Content-Type: application/pdf" \ --data-binary @report.pdf # Upload image to public bucket curl -X PUT "https://your-worker.workers.dev/object?key=images/photo.png&public=true" \ -H "Content-Type: image/png" \ --data-binary @photo.png # Upload with custom metadata curl -X PUT "https://your-worker.workers.dev/object?key=files/data.json&public=true" \ -H "Content-Type: application/json" \ -H "X-Custom-Metadata-Author: Alice" \ -H "X-Custom-Metadata-Version: 1.0" \ --data-binary @data.json ``` ```json Private upload response { "key": "documents/report.pdf", "uploaded": true } ``` ```json Public upload response { "key": "images/photo.png", "uploaded": true, "url": "https://pub-xxxxx.r2.dev/images/photo.png" } ``` <Callout type="idea"> The `url` in the response is a direct public link. You can share it or use it in `<img>` tags without going through the Worker. </Callout> *** ### Delete object [#delete-object] <Card title="DELETE /object"> Delete an object from R2. </Card> #### Query Parameters [#query-parameters-4] **`key`** `string` (required) Object key to delete **`public`** `boolean` Set to `true` or `1` to delete from the public bucket #### Response [#response-4] **`key`** `string` Object key that was deleted **`deleted`** `boolean` Always `true` on success #### Example [#example-4] ```bash # Delete from private bucket curl -X DELETE "https://your-worker.workers.dev/object?key=temp/old-file.txt" # Delete from public bucket curl -X DELETE "https://your-worker.workers.dev/object?key=images/old-photo.png&public=true" ``` <Tabs items="['Response']"> <Tab value="Response"> ```json { "key": "temp/old-file.txt", "deleted": true } ``` </Tab> </Tabs> ## Setup [#setup] <Steps> <Step> ### Create R2 buckets [#create-r2-buckets] In the [Cloudflare dashboard](https://dash.cloudflare.com/) → **R2** → **Create bucket**: 1. Create `r2-storage-bucket` (private - default settings) 2. Create `r2-storage-public` (for public files) </Step> <Step> ### Enable public access [#enable-public-access] For the **public bucket only**: 1. Go to **R2** → select `r2-storage-public` → **Settings** 2. Under **Public Development URL**, click **Enable** 3. Type `allow` when prompted and confirm 4. Copy the public bucket URL (looks like `https://pub-xxxxx.r2.dev`) <Callout> You can also use a custom domain instead of the `r2.dev` URL </Callout> </Step> <Step> ### Configure wrangler.json [#configure-wranglerjson] Update your `wrangler.json` with the bucket bindings: ```json { "r2_buckets": [ { "binding": "BUCKET", "bucket_name": "r2-storage-bucket" }, { "binding": "PUBLIC_BUCKET", "bucket_name": "r2-storage-public" } ], "vars": { "PUBLIC_BUCKET_URL": "https://pub-xxxxx.r2.dev" } } ``` Replace `https://pub-xxxxx.r2.dev` with your actual public bucket URL. </Step> <Step> ### Run locally [#run-locally] ```bash cd apps/experiments/r2-storage npm install npm run dev -- --remote ``` <Callout> Use `--remote` to connect to real R2 buckets. The public URL won't work with local buckets. </Callout> </Step> </Steps> ## Error Responses [#error-responses] All errors return JSON with this structure: ```json { "error": "Error message", "code": "ERROR_CODE" } ``` **`error`** `string` Human-readable error message **`code`** `string` Error code: `INVALID_QUERY`, `NOT_FOUND`, `MISSING_PARAM`, `INTERNAL_ERROR` ## Source Code [#source-code] View the complete source code on GitHub: [r2-storage](https://github.com/shrinathsnayak/cloudflare-experiments/tree/main/apps/experiments/r2-storage) ## Use Cases [#use-cases] ### Image hosting [#image-hosting] Upload images to the public bucket and use the returned URL in your app: ```bash # Upload curl -X PUT "https://your-worker.workers.dev/object?key=avatars/user123.jpg&public=true" \ -H "Content-Type: image/jpeg" \ --data-binary @avatar.jpg # Response: { "url": "https://pub-xxxxx.r2.dev/avatars/user123.jpg", ... } # Use URL directly: <img src="https://pub-xxxxx.r2.dev/avatars/user123.jpg" /> ``` ### Private file storage [#private-file-storage] Store documents that should only be accessible through your Worker: ```bash # Upload curl -X PUT "https://your-worker.workers.dev/object?key=user-data/123/invoice.pdf" \ -H "Content-Type: application/pdf" \ --data-binary @invoice.pdf # Download (with auth check in your Worker) curl "https://your-worker.workers.dev/object?key=user-data/123/invoice.pdf" \ -H "Authorization: Bearer token" ``` ### Organized file structure [#organized-file-structure] Use prefixes and delimiters for directory-like organization: ```bash # List all images curl "https://your-worker.workers.dev/list?prefix=images/&public=true" # List top-level "directories" only curl "https://your-worker.workers.dev/list?delimiter=/&public=true" ``` ## Limitations [#limitations] * No authentication on API endpoints in this demo * List operations return at most 1,000 keys per request * Requires R2 bucket bindings and optional public URL configuration * Not a multipart upload or large-file streaming demo ## Deployment [#deployment] [![Deploy to Cloudflare Workers](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/shrinathsnayak/cloudflare-experiments/tree/main/apps/experiments/r2-storage) <Steps> <Step> ### Create buckets [#create-buckets] Create both R2 buckets and enable public access on the public one (see Setup above) </Step> <Step> ### Deploy Worker [#deploy-worker] Click the deploy button or run: ```bash npm run deploy ``` </Step> <Step> ### Configure variables [#configure-variables] In **Workers & Pages** → your Worker → **Settings** → **Variables**: * Add `PUBLIC_BUCKET_URL` = your public bucket URL (e.g., `https://pub-xxxxx.r2.dev`) </Step> <Step> ### Update wrangler.json [#update-wranglerjson] Ensure `bucket_name` values match your actual bucket names in the dashboard </Step> </Steps> ## Configuration [#configuration] ### Environment Variables [#environment-variables] **`param`** `string` Base URL for the public R2 bucket (e.g., `https://pub-xxxxx.r2.dev`). Required to return public URLs in upload responses. Set in `wrangler.json` under `vars` or in the dashboard under **Workers & Pages** → **Settings** → **Variables**. ### Bindings [#bindings] **`param`** `R2Bucket` Private R2 bucket binding (name: `r2-storage-bucket`) **`param`** `R2Bucket` Public R2 bucket binding (name: `r2-storage-public`) ## Cloudflare Features Used [#cloudflare-features-used] * **[Workers](https://developers.cloudflare.com/workers/)** - Serverless compute at the edge * **[R2](https://developers.cloudflare.com/r2/)** - Object storage with zero egress fees * **[Public buckets](https://developers.cloudflare.com/r2/buckets/public-buckets/)** - Direct public access to objects * **[Hono](https://hono.dev/)** - Lightweight web framework # RAG Mini Search (/docs/experiments/rag-mini-search) A minimal **retrieval-augmented generation** demo: embed a small corpus of experiment descriptions into **Vectorize**, retrieve top matches for a question, and generate a grounded answer with **Workers AI** plus cited source titles. ## Features [#features] * **POST /seed** - Embed and upsert demo experiment docs into Vectorize * **POST /ask** - Question → retrieve → generate grounded answer * **Cited sources** - Response includes matched experiment titles and scores * **Demo corpus** - 10 experiment descriptions from this repo ## API Reference [#api-reference] ### POST /seed [#post-seed] Load the demo document corpus into Vectorize. Run once after deploy or locally before asking questions. #### Example Request [#example-request] ```bash curl -X POST "https://your-worker.workers.dev/seed" ``` #### Success Response [#success-response] ```json { "seeded": 10, "documents": ["Is It Down", "Link Shortener", "Cloud AI Proxy"] } ``` #### Error Codes [#error-codes] * `502` - Seeding failed (`SEED_ERROR`) ### POST /ask [#post-ask] Ask a question against the indexed corpus. <TypeTable type="{ question: { description: "Natural language question (max 500 characters).", type: "string", required: true, }, }" /> #### Example Request [#example-request-1] ```bash curl -X POST "https://your-worker.workers.dev/ask" \ -H "Content-Type: application/json" \ -d '{"question":"Which experiment uses Durable Objects?"}' ``` #### Success Response [#success-response-1] ```json { "question": "Which experiment uses Durable Objects?", "answer": "The Durable Counter experiment uses Durable Objects for a globally consistent counter (Durable Counter).", "sources": [{ "id": "durable-counter", "title": "Durable Counter", "score": 0.89 }] } ``` #### Error Codes [#error-codes-1] * `400` - Invalid JSON (`INVALID_BODY`) or question (`INVALID_QUESTION`) * `404` - No indexed documents; run `POST /seed` first (`NOT_INDEXED`) * `502` - RAG pipeline error (`RAG_ERROR`) ## Use Cases [#use-cases] * Learn Vectorize + Workers AI RAG patterns end-to-end * Prototype documentation or support bots over a small corpus * Compare embedding retrieval quality before scaling to larger indexes ## Limitations [#limitations] * Demo corpus is fixed (10 documents); not auto-synced with repo changes * Must call `POST /seed` before `POST /ask` * Requires Vectorize index `rag-mini-search` and Workers AI ## Deployment [#deployment] <Steps> <Step> ### Click the deploy button [#click-the-deploy-button] [![Deploy to Cloudflare Workers](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/shrinathsnayak/cloudflare-experiments/tree/main/apps/experiments/rag-mini-search) </Step> <Step> ### Create Vectorize index [#create-vectorize-index] Create index `rag-mini-search` (or update `wrangler.json`), then seed: ```bash curl -X POST "https://your-worker.workers.dev/seed" ``` </Step> </Steps> ## Local Development [#local-development] ```bash cd apps/experiments/rag-mini-search npm install npm run dev npm run seed ``` ## Configuration [#configuration] | Binding | Purpose | | ----------- | -------------------------------------------------------------------------------------- | | `AI` | Embeddings (`@cf/baai/bge-base-en-v1.5`) + LLM (`@cf/meta/llama-3.1-8b-instruct-fast`) | | `VECTORIZE` | Vector index `rag-mini-search` | ## Cloudflare Features Used [#cloudflare-features-used] * **[Workers](https://developers.cloudflare.com/workers/)** - Edge compute runtime * **[Workers AI](https://developers.cloudflare.com/workers-ai/)** - Embeddings and text generation * **[Vectorize](https://developers.cloudflare.com/vectorize/)** - Vector search at the edge # Rate Limiter Demo (/docs/experiments/rate-limiter-demo) Demonstrates the native **Workers Rate Limiting** binding. `GET /limited` enforces per-key limits and returns **429** with `Retry-After` when exceeded. `GET /status` shows configured limits and demo usage counters stored in KV. ## Features [#features] * **GET /limited** - Rate-limited endpoint (10 req / 60s per key) * **GET /status?key=** - View limit config and usage counters * **429 responses** - Includes `Retry-After` header and `RATE_LIMITED` code * **Per-key limiting** - Defaults to client IP; override with `?key=` ## API Reference [#api-reference] ### GET /limited [#get-limited] Request the rate-limited resource. **`key`** `string` (optional query) Override rate limit key (max 128 chars). Defaults to `CF-Connecting-IP`. #### Example Request [#example-request] ```bash curl "https://your-worker.workers.dev/limited" curl "https://your-worker.workers.dev/limited?key=demo-client" ``` #### Success Response [#success-response] ```json { "message": "Request allowed", "key": "203.0.113.1", "limit": 10, "periodSeconds": 60 } ``` #### Rate Limited Response (429) [#rate-limited-response-429] ```json { "error": "Rate limit exceeded", "code": "RATE_LIMITED", "key": "203.0.113.1", "retryAfterSeconds": 60 } ``` Response includes header: `Retry-After: 60` ### GET /status [#get-status] View rate limit configuration and demo usage counters. **`key`** `string` (optional query) Key to inspect. Defaults to client IP. #### Example Request [#example-request-1] ```bash curl "https://your-worker.workers.dev/status?key=demo-client" ``` #### Success Response [#success-response-1] ```json { "key": "demo-client", "config": { "limit": 10, "periodSeconds": 60 }, "usage": { "key": "demo-client", "allowed": 7, "blocked": 3, "lastSeen": "2025-06-20T12:00:00.000Z" }, "note": "Native Rate Limiting binding enforces limits per PoP..." } ``` ## Use Cases [#use-cases] * Learn the Workers Rate Limiting binding (`limit()` API) * Prototype API throttling before production WAF rules * Demonstrate 429 + Retry-After client handling ## Limitations [#limitations] * Rate limits are enforced per PoP by the native binding * KV usage counters are for demo visualization only (not authoritative) * Configured limit: 10 requests per 60 seconds (see `wrangler.json`) ## Deployment [#deployment] <Steps> <Step> ### Click the deploy button [#click-the-deploy-button] [![Deploy to Cloudflare Workers](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/shrinathsnayak/cloudflare-experiments/tree/main/apps/experiments/rate-limiter-demo) </Step> <Step> ### Configure KV [#configure-kv] Create a KV namespace bound as `USAGE`. Rate limit binding is declared in `wrangler.json`. </Step> <Step> ### Test rate limiting [#test-rate-limiting] ```bash for i in $(seq 1 12); do curl -s -o /dev/null -w "%{http_code}\n" "https://your-worker.workers.dev/limited"; done ``` </Step> </Steps> ## Local Development [#local-development] ```bash cd apps/experiments/rate-limiter-demo npm install npm run dev ``` ## Configuration [#configuration] | Binding / setting | Purpose | | ----------------- | ------------------------------------ | | `RATE_LIMITER` | Native rate limiting (10 req / 60s) | | `USAGE` | KV namespace for demo usage counters | ## Cloudflare Features Used [#cloudflare-features-used] * **[Workers](https://developers.cloudflare.com/workers/)** - Edge compute runtime * **[Rate Limiting binding](https://developers.cloudflare.com/workers/runtime-apis/bindings/rate-limit/)** - Native per-key throttling * **[Workers KV](https://developers.cloudflare.com/kv/)** - Demo usage tracking # Readability Extractor (/docs/experiments/readability-extractor) Load a fully rendered page with **Browser Rendering**, then strip navigation, ads, and sidebars using readability-style heuristics. Returns title, author (when detectable), body text, word count, and estimated read time. ## Features [#features] * GET /extract - rendered DOM extraction with readability heuristics * Returns title, author, body, wordCount, readTimeMinutes * Uses @cloudflare/puppeteer like other Browser Rendering experiments ## API Reference [#api-reference] ### GET /extract [#get-extract] **`url`** `string` (required) - Article URL (http or https). #### Example Request [#example-request] ```bash curl "https://your-worker.workers.dev/extract?url=https://example.com/article" ``` #### Error Codes [#error-codes] * `400` - `INVALID_URL` * `502` - `EXTRACT_ERROR` ## Use Cases [#use-cases] * Build reading-mode or newsletter digest pipelines * Extract main content from JavaScript-heavy news sites * Prototype RAG document ingestion from article URLs ## Limitations [#limitations] * Requires Browser Rendering on your account * Heuristic extraction; not identical to Mozilla Readability * Local dev may need `wrangler dev --remote` for browser binding ## Deployment [#deployment] <Steps> <Step> ### Click the deploy button [#click-the-deploy-button] [![Deploy to Cloudflare Workers](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/shrinathsnayak/cloudflare-experiments/tree/main/apps/experiments/readability-extractor) </Step> <Step> ### Configure bindings [#configure-bindings] Browser binding `BROWSER` and `nodejs_compat_v2` in `wrangler.json`. </Step> <Step> ### Test your deployment [#test-your-deployment] See the experiment README for curl examples. </Step> </Steps> ## Local Development [#local-development] ```bash cd apps/experiments/readability-extractor npm install npm run dev ``` ## Configuration [#configuration] Browser binding `BROWSER` and `nodejs_compat_v2` in `wrangler.json`. ## Cloudflare Features Used [#cloudflare-features-used] * **[Browser Rendering](https://developers.cloudflare.com/browser-rendering/)** * **[Workers](https://developers.cloudflare.com/workers/)** # Rendered Text (/docs/experiments/rendered-text) Load any URL in a headless browser and extract the visible page text after JavaScript has executed. ## API Reference [#api-reference] ### GET /text [#get-text] Returns the document title and normalized visible text from `document.body.innerText`. <TypeTable type="{ url: { description: "The website URL to extract text from (must be http or https)", type: "string", required: true, }, }" /> #### Example Request [#example-request] ```bash curl "https://your-worker.workers.dev/text?url=https://www.cloudflare.com" ``` #### Success Response [#success-response] **`url`** `string` The normalized URL that was loaded **`title`** `string` The page `<title>` after rendering **`text`** `string` Visible text with whitespace collapsed **`textLength`** `number` Length of the returned text in characters **`truncated`** `boolean` Whether the text was truncated at 50,000 characters ```json { "url": "https://www.cloudflare.com/", "title": "Connect, protect, and build everywhere", "text": "Connect, protect, and build everywhere ...", "textLength": 1234, "truncated": false } ``` #### Error Response [#error-response] ```json { "error": "Missing or invalid query parameter: url", "code": "INVALID_URL" } ``` #### Error Codes [#error-codes] * `400` - Missing or invalid `url` parameter * `502` - Navigation or extraction failed (`RENDER_ERROR`) ## Use Cases [#use-cases] * Scrape single-page apps and client-rendered content * Feed rendered page text to LLM pipelines * Compare static fetch vs browser-rendered output * Build search indexes for JavaScript-heavy sites ## Limitations [#limitations] * Requires Browser Rendering enabled on your Cloudflare account * Local development needs `npx wrangler dev --remote` * Extracted text is truncated at a maximum length * Navigation timeout on slow or script-heavy pages ## Deployment [#deployment] <Steps> <Step> ### Click the deploy button [#click-the-deploy-button] [![Deploy to Cloudflare Workers](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/shrinathsnayak/cloudflare-experiments/tree/main/apps/experiments/rendered-text) </Step> <Step> ### Enable Browser Rendering [#enable-browser-rendering] Browser Rendering must be enabled on your account. </Step> <Step> ### Test your deployment [#test-your-deployment] ```bash curl "https://your-worker.workers.dev/text?url=https://www.cloudflare.com" ``` </Step> </Steps> ## Local Development [#local-development] ```bash cd apps/experiments/rendered-text npm install npm run dev ``` Test locally: ```bash curl "http://localhost:8787/text?url=https://www.cloudflare.com" ``` ## Cloudflare Features Used [#cloudflare-features-used] * **[Workers](https://developers.cloudflare.com/workers/)** - Edge compute runtime * **[Browser Rendering](https://developers.cloudflare.com/browser-rendering/)** - Headless Chromium via `@cloudflare/puppeteer` * **[Puppeteer DOM evaluation](https://developers.cloudflare.com/browser-rendering/platform/puppeteer/)** - Extract rendered text after `networkidle0` # Response Headers (/docs/experiments/response-headers) Fetch any URL and return its HTTP status and response headers as JSON. ## API Reference [#api-reference] ### GET /headers [#get-headers] Uses `HEAD` first and falls back to `GET` when the origin does not support `HEAD`. <TypeTable type="{ url: { description: "The target URL (must be http or https)", type: "string", required: true, }, }" /> #### Example Request [#example-request] ```bash curl "https://your-worker.workers.dev/headers?url=https://www.cloudflare.com" ``` #### Success Response [#success-response] **`url`** `string` Final URL after redirects **`statusCode`** `number` HTTP status code **`statusText`** `string` HTTP status text **`method`** `string` `HEAD` or `GET` **`headers`** `object` Response headers as key/value pairs ```json { "url": "https://www.cloudflare.com/", "statusCode": 200, "statusText": "OK", "method": "HEAD", "headers": { "content-type": "text/html; charset=utf-8", "server": "cloudflare" } } ``` #### Error Codes [#error-codes] * `400` - Missing or invalid `url` (`INVALID_URL`) * `502` - Fetch failed (`FETCH_ERROR`) ## Use Cases [#use-cases] * Debug cache and security headers from the edge * Compare response headers across origins * Learn HEAD vs GET fallback patterns in Workers ## Limitations [#limitations] * Uses HEAD with GET fallback; some origins return different headers per method * Single URL per request * Fetch timeout on slow origins ## Deployment [#deployment] <Steps> <Step> ### Click the deploy button [#click-the-deploy-button] [![Deploy to Cloudflare Workers](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/shrinathsnayak/cloudflare-experiments/tree/main/apps/experiments/response-headers) </Step> <Step> ### Test your deployment [#test-your-deployment] ```bash curl "https://your-worker.workers.dev/headers?url=https://www.cloudflare.com" ``` </Step> </Steps> ## Local Development [#local-development] ```bash cd apps/experiments/response-headers npm install npm run dev ``` ## Cloudflare Features Used [#cloudflare-features-used] * **[Workers](https://developers.cloudflare.com/workers/)** - Edge compute runtime * **[Fetch API](https://developers.cloudflare.com/workers/runtime-apis/fetch/)** - HTTP requests from the edge # Robots Sitemap Inspector (/docs/experiments/robots-sitemap-inspector) Fetch and parse `robots.txt` plus linked sitemaps for SEO and AI-crawler debugging from Cloudflare's edge. ## Features [#features] * Parses `User-agent`, `Allow`, `Disallow`, and `Sitemap` directives * Inspects up to 5 sitemap URLs from robots.txt * Distinguishes `urlset` vs `sitemapindex` and returns sample URLs * Stateless; no bindings required ## API Reference [#api-reference] ### GET /inspect [#get-inspect] <TypeTable type="{ url: { description: "Any http or https URL. The origin's /robots.txt is fetched.", type: "string", required: true, }, }" /> #### Example Request [#example-request] ```bash curl "https://your-worker.workers.dev/inspect?url=https://www.cloudflare.com" ``` #### Success Response [#success-response] **`url`** `string` Input URL that was inspected **`robots`** `object` Parsed robots.txt: `present`, `url`, `groups`, `sitemaps`, optional `rawPreview` / `error` **`sitemaps`** `array` Per-sitemap results with `ok`, `type`, `urlCount`, `sampleUrls` or `childSitemaps` ```json { "url": "https://www.cloudflare.com/", "robots": { "present": true, "url": "https://www.cloudflare.com/robots.txt", "groups": [{ "userAgent": "*", "allow": [], "disallow": ["/cdn-cgi/"] }], "sitemaps": ["https://www.cloudflare.com/sitemap.xml"] }, "sitemaps": [ { "url": "https://www.cloudflare.com/sitemap.xml", "ok": true, "type": "sitemapindex", "urlCount": 3, "childSitemaps": ["https://www.cloudflare.com/sitemap-0.xml"] } ] } ``` #### Error Codes [#error-codes] * `400` - Missing or invalid `url` (`INVALID_URL`) * `502` - Unexpected fetch failure (`FETCH_ERROR`) Missing `robots.txt` (404) still returns `200` with `robots.present: false`. ## Use Cases [#use-cases] * Debug crawl rules before launching content * Verify sitemap discovery from robots.txt * Complement AI bot visibility checks with full robots/sitemap context * Audit SEO crawl configuration from the edge ## Limitations [#limitations] * Fetches at most 5 sitemaps and samples a small set of URLs * Does not recursively expand every child sitemap in an index * Robots matching semantics are summarized, not a full crawler simulator ## Deployment [#deployment] <Steps> <Step> ### Click the deploy button [#click-the-deploy-button] [![Deploy to Cloudflare Workers](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/shrinathsnayak/cloudflare-experiments/tree/main/apps/experiments/robots-sitemap-inspector) </Step> <Step> ### Deploy [#deploy] Follow the deployment wizard. No bindings or secrets required. </Step> <Step> ### Test your deployment [#test-your-deployment] ```bash curl "https://your-worker.workers.dev/inspect?url=https://example.com" ``` </Step> </Steps> ## Local Development [#local-development] ```bash cd apps/experiments/robots-sitemap-inspector npm install npm run dev ``` ```bash curl "http://localhost:8787/inspect?url=https://www.cloudflare.com" ``` ## Cloudflare Features Used [#cloudflare-features-used] * **[Workers](https://developers.cloudflare.com/workers/)** - Edge compute runtime * **[Fetch API](https://developers.cloudflare.com/workers/runtime-apis/fetch/)** - Fetch robots.txt and sitemaps ## Next Steps [#next-steps] <Cards> <Card title="AI Bot Visibility" href="/docs/experiments/ai-bot-visibility"> Check AI crawler allow/block configuration </Card> <Card title="Website Metadata Extractor" href="/docs/experiments/website-metadata-extractor"> Extract title, description, and Open Graph tags </Card> </Cards> # RSS / Atom Feed Parser (/docs/experiments/rss-atom-feed-parser) Fetch an RSS or Atom feed URL and return a normalized JSON representation of the channel and items from Cloudflare's edge. ## Features [#features] * Supports RSS 2.0 and Atom * Normalizes title, link, id, published date, summary, and author * Caps results at 50 items * Stateless; no bindings required ## API Reference [#api-reference] ### GET /parse [#get-parse] Fetches the feed URL and returns a normalized channel + items payload. <TypeTable type="{ url: { description: "Feed URL (must be http or https)", type: "string", required: true, }, }" /> #### Example Request [#example-request] ```bash curl "https://your-worker.workers.dev/parse?url=https://blog.cloudflare.com/rss/" ``` #### Success Response [#success-response] **`url`** `string` Final feed URL after redirects **`format`** `string` `"rss"`, `"atom"`, or `"unknown"` **`title`** `string` Feed/channel title when present **`description`** `string` Feed description or Atom subtitle when present **`link`** `string` Feed homepage / self link when present **`itemCount`** `number` Number of items returned (max 50) **`items`** `array` Normalized items with `title`, optional `link`, `id`, `published`, `summary`, `author` ```json { "url": "https://blog.cloudflare.com/rss/", "format": "rss", "title": "The Cloudflare Blog", "description": "Cloudflare Blog", "link": "https://blog.cloudflare.com/", "itemCount": 2, "items": [ { "title": "Example post", "link": "https://blog.cloudflare.com/example/", "id": "https://blog.cloudflare.com/example/", "published": "Mon, 01 Jan 2024 00:00:00 GMT", "summary": "Post summary…" } ] } ``` #### Error Response [#error-response] ```json { "error": "URL did not return a recognizable RSS or Atom feed", "code": "NOT_FEED" } ``` #### Error Codes [#error-codes] * `400` - Missing or invalid `url` (`INVALID_URL`) * `400` - Response was not a recognizable feed (`NOT_FEED`) * `502` - Fetch failed (`FETCH_ERROR`) ## Use Cases [#use-cases] * Power newsletter or digest pipelines * Normalize third-party feeds for dashboards * Convert blog/changelog feeds into structured JSON APIs * Poll product update feeds from Workers cron jobs ## Limitations [#limitations] * Heuristic XML parsing (not a full XML DOM) * Does not follow pagination or feed hubs * Large feeds are truncated to 50 items * Does not execute JavaScript-generated feeds ## Deployment [#deployment] <Steps> <Step> ### Click the deploy button [#click-the-deploy-button] [![Deploy to Cloudflare Workers](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/shrinathsnayak/cloudflare-experiments/tree/main/apps/experiments/rss-atom-feed-parser) </Step> <Step> ### Deploy [#deploy] Follow the deployment wizard. No bindings or secrets required. </Step> <Step> ### Test your deployment [#test-your-deployment] ```bash curl "https://your-worker.workers.dev/parse?url=https://blog.cloudflare.com/rss/" ``` </Step> </Steps> ## Local Development [#local-development] ```bash cd apps/experiments/rss-atom-feed-parser npm install npm run dev ``` Test locally: ```bash curl "http://localhost:8787/parse?url=https://blog.cloudflare.com/rss/" ``` ## Cloudflare Features Used [#cloudflare-features-used] * **[Workers](https://developers.cloudflare.com/workers/)** - Edge compute runtime * **[Fetch API](https://developers.cloudflare.com/workers/runtime-apis/fetch/)** - Feed retrieval from the edge ## Next Steps [#next-steps] <Cards> <Card title="Website to llms.txt" href="/docs/experiments/website-to-llms-txt"> Convert webpage content into llms.txt for LLM consumption </Card> <Card title="JSON-LD Extractor" href="/docs/experiments/json-ld-extractor"> Extract Schema.org structured data from pages </Card> </Cards> # Screenshot API (/docs/experiments/screenshot-api) Capture screenshots of any website from Cloudflare's edge network using Browser Rendering and Puppeteer. ## API Reference [#api-reference] ### GET /screenshot [#get-screenshot] Captures a PNG screenshot of the specified URL. <TypeTable type="{ url: { description: "The website URL to screenshot (must be http or https)", type: "string", required: true, }, }" /> #### Example Request [#example-request] ```bash curl "https://your-worker.workers.dev/screenshot?url=https://www.cloudflare.com" \ --output screenshot.png ``` #### Success Response [#success-response] Returns a PNG image with the following headers: ```http Content-Type: image/png Cache-Control: public, max-age=60 ``` #### Error Response [#error-response] **`error`** `string` Human-readable error message **`code`** `string` Error code indicating the failure type ```json { "error": "Missing or invalid query parameter: url", "code": "INVALID_URL" } ``` #### Error Codes [#error-codes] * `400` - Missing or invalid `url` parameter (not http/https) * `502` - Screenshot operation failed (navigation timeout, unreachable site, rendering error) ## Implementation Details [#implementation-details] The API uses the following configuration: * **Viewport**: 1280x800 pixels * **Wait condition**: `networkidle0` (waits until network is idle) * **Navigation timeout**: 20 seconds * **Image format**: PNG ## Use Cases [#use-cases] * Capture visual snapshots of web pages from the edge * Generate thumbnails or previews for link-sharing tools * Archive how a page looked at a point in time * Learn Browser Rendering with Puppeteer on Workers ## Limitations [#limitations] * Requires Browser Rendering enabled on your Cloudflare account * Fixed 1280×800 viewport; no custom dimensions in this experiment * 20-second navigation timeout; `networkidle0` may fail on busy sites * Local development needs `npx wrangler dev --remote` ## Deployment [#deployment] <Steps> <Step> ### Click the deploy button [#click-the-deploy-button] [![Deploy to Cloudflare Workers](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/shrinathsnayak/cloudflare-experiments/tree/main/apps/experiments/screenshot-api) </Step> <Step> ### Enable Browser Rendering [#enable-browser-rendering] This experiment requires Cloudflare Browser Rendering to be enabled on your account. Visit the [Browser Rendering docs](https://developers.cloudflare.com/browser-rendering/) for setup instructions. </Step> <Step> ### Deploy [#deploy] Follow the deployment wizard to deploy the Worker to your Cloudflare account. </Step> </Steps> ## Local Development [#local-development] ```bash cd apps/experiments/screenshot-api npm install npx wrangler dev --remote ``` <Callout> Use `--remote` flag to access a real browser instance. Local development without this flag will not work as Browser Rendering requires Cloudflare infrastructure. </Callout> Test locally: ```bash curl "http://localhost:8787/screenshot?url=https://www.cloudflare.com" \ --output test.png ``` ## Cloudflare Features Used [#cloudflare-features-used] * **[Workers](https://developers.cloudflare.com/workers/)** - Edge compute runtime * **[Browser Rendering](https://developers.cloudflare.com/browser-rendering/)** - Headless Chromium via Puppeteer * **[Fetch API](https://developers.cloudflare.com/workers/runtime-apis/fetch/)** - Network requests within the browser # Security Headers Grader (/docs/experiments/security-headers-grader) Fetch any URL from Cloudflare's edge and score its security headers with pass/warn/fail guidance. ## Features [#features] * Grades HSTS, CSP, clickjacking protection, `X-Content-Type-Options`, `Referrer-Policy`, `Permissions-Policy`, and `Cross-Origin-Opener-Policy` * Returns a numeric score (0–100) and letter grade (A–F) * Uses HEAD with GET fallback when origins reject HEAD * Stateless; no bindings required ## API Reference [#api-reference] ### GET /grade [#get-grade] <TypeTable type="{ url: { description: "The target URL (must be http or https)", type: "string", required: true, }, }" /> #### Example Request [#example-request] ```bash curl "https://your-worker.workers.dev/grade?url=https://www.cloudflare.com" ``` #### Success Response [#success-response] **`url`** `string` Final URL after redirects **`score`** `number` Average score from 0–100 across all checks **`grade`** `string` Letter grade: `A`, `B`, `C`, `D`, or `F` **`checks`** `array` Per-header results with `header`, `status` (`pass` | `warn` | `fail` | `missing`), `detail`, and `recommendation` **`headers`** `object` Raw response headers as lowercase key/value pairs ```json { "url": "https://www.cloudflare.com/", "score": 71, "grade": "C", "checks": [ { "header": "Strict-Transport-Security", "status": "pass", "detail": "max-age=31536000; includeSubDomains; preload", "recommendation": "Looks good" } ], "headers": { "strict-transport-security": "max-age=31536000; includeSubDomains; preload" } } ``` #### Error Codes [#error-codes] * `400` - Missing or invalid `url` (`INVALID_URL`) * `502` - Fetch failed (`FETCH_ERROR`) ## Use Cases [#use-cases] * Audit security headers before launching a site * Compare header posture across environments * Teach Workers fetch + header analysis patterns * Generate actionable remediation tips for missing headers ## Limitations [#limitations] * Scoring is heuristic, not a formal compliance audit * Does not execute or fully validate CSP policy correctness * Only inspects response headers visible to the edge fetch ## Deployment [#deployment] <Steps> <Step> ### Click the deploy button [#click-the-deploy-button] [![Deploy to Cloudflare Workers](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/shrinathsnayak/cloudflare-experiments/tree/main/apps/experiments/security-headers-grader) </Step> <Step> ### Deploy [#deploy] Follow the deployment wizard. No bindings or secrets required. </Step> <Step> ### Test your deployment [#test-your-deployment] ```bash curl "https://your-worker.workers.dev/grade?url=https://example.com" ``` </Step> </Steps> ## Local Development [#local-development] ```bash cd apps/experiments/security-headers-grader npm install npm run dev ``` ```bash curl "http://localhost:8787/grade?url=https://www.cloudflare.com" ``` ## Cloudflare Features Used [#cloudflare-features-used] * **[Workers](https://developers.cloudflare.com/workers/)** - Edge compute runtime * **[Fetch API](https://developers.cloudflare.com/workers/runtime-apis/fetch/)** - HEAD/GET requests from the edge * **[Edge network](https://developers.cloudflare.com/workers/reference/how-workers-works/)** - Low-latency header inspection ## Next Steps [#next-steps] <Cards> <Card title="Response Headers" href="/docs/experiments/response-headers"> Dump raw response headers without scoring </Card> <Card title="CORS Preflight Tester" href="/docs/experiments/cors-preflight-tester"> Simulate browser CORS preflight OPTIONS requests </Card> </Cards> # Sentiment Analyzer (/docs/experiments/sentiment-analyzer) Classify text sentiment as positive or negative using [Workers AI](https://developers.cloudflare.com/workers-ai/) and DistilBERT. Returns a label and confidence score for any short text input. ## Features [#features] * Classify text as positive or negative with confidence scores * Workers AI DistilBERT model at the edge * Single GET endpoint with a `text` query parameter * No external ML API keys required ## API Reference [#api-reference] ### GET /sentiment [#get-sentiment] Analyzes the sentiment of the provided text. <TypeTable type="{ text: { description: "Text to classify (max 10,000 characters)", type: "string", required: true, }, }" /> #### Example Request [#example-request] ```bash curl "https://your-worker.workers.dev/sentiment?text=This%20pizza%20is%20great!" ``` #### Success Response [#success-response] **`text`** `string` The original input text **`label`** `string` Sentiment label (e.g. `POSITIVE` or `NEGATIVE`) **`score`** `number` Confidence score for the predicted label (0–1) ```json { "text": "This pizza is great!", "label": "POSITIVE", "score": 0.9998 } ``` #### Error Response [#error-response] ```json { "error": "Missing or invalid query parameter: text", "code": "INVALID_TEXT" } ``` #### Error Codes [#error-codes] * `400` - Missing or invalid `text` (`INVALID_TEXT`) * `502` - Workers AI model run failed (`AI_ERROR`) ## Use Cases [#use-cases] * Score customer feedback and reviews at the edge * Filter or route messages by sentiment in real time * Learn Workers AI classification models in a stateless Worker * Build moderation or analytics pipelines without external ML APIs ## Limitations [#limitations] * Workers AI is subject to [usage limits](https://developers.cloudflare.com/workers-ai/platform/limits/) by plan * Input text length is capped * Accuracy varies on very short, sarcastic, or multilingual text ## Deployment [#deployment] <Steps> <Step> ### Click the deploy button [#click-the-deploy-button] [![Deploy to Cloudflare Workers](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/shrinathsnayak/cloudflare-experiments/tree/main/apps/experiments/sentiment-analyzer) </Step> <Step> ### Deploy [#deploy] Enable the **Workers AI** binding (`AI`) in your Worker settings. The deploy button configures this automatically via `wrangler.json`. Requires a Cloudflare account with Workers AI enabled. </Step> <Step> ### Test your deployment [#test-your-deployment] ```bash curl "https://your-worker.workers.dev/sentiment?text=This%20pizza%20is%20great!" ``` </Step> </Steps> ## Local Development [#local-development] ```bash cd apps/experiments/sentiment-analyzer npm install npm run dev ``` Test locally: ```bash curl "http://localhost:8787/sentiment?text=This%20pizza%20is%20great!" ``` ## Cloudflare Features Used [#cloudflare-features-used] * **[Workers](https://developers.cloudflare.com/workers/)** - Edge compute runtime * **[Workers AI](https://developers.cloudflare.com/workers-ai/)** - `@cf/huggingface/distilbert-sst-2-int8` sentiment classification model # Social Preview Inspector (/docs/experiments/social-preview-inspector) Fetch any URL and extract `og:*`, `twitter:*`, `<title>`, and meta description tags with **HTMLRewriter**. Returns side-by-side preview validation for Twitter/X, Open Graph (Facebook/Slack), and Google search snippets, flagging missing required fields per platform. ## Features [#features] * GET /inspect - extract meta tags and validate per-platform preview requirements * Side-by-side Twitter, Open Graph, and Google preview objects * Flags missing and fallback fields (e.g. og:title used when twitter:title absent) ## API Reference [#api-reference] ### GET /inspect [#get-inspect] Fetch a page and return social preview validation for three platforms. <TypeTable type="{ url: { description: "Target URL (http or https only).", type: "string", required: true, }, }" /> #### Example Request [#example-request] ```bash curl "https://your-worker.workers.dev/inspect?url=https://example.com" ``` #### Success Response [#success-response] ```json { "url": "https://example.com/", "extracted": { "title": "Example", "description": "...", "openGraph": {}, "twitter": {} }, "previews": { "openGraph": { "platform": "openGraph", "valid": true, "missing": [], "fields": {} }, "twitter": { "platform": "twitter", "valid": false, "missing": ["image"], "fields": {} }, "google": { "platform": "google", "valid": true, "missing": [], "fields": {} } } } ``` #### Error Codes [#error-codes] * `400` - `INVALID_URL` * `502` - `FETCH_ERROR` ## Use Cases [#use-cases] * Debug why a link unfurls incorrectly on Slack, Twitter, or iMessage * Audit marketing pages for missing Open Graph or Twitter Card tags * Compare Google snippet fields against social preview metadata ## Limitations [#limitations] * Uses raw HTML fetch; does not execute JavaScript (client-rendered tags may be missing) * Single URL per request; no batch inspection * Platform validation rules are heuristic, not identical to each platform's renderer ## Deployment [#deployment] <Steps> <Step> ### Click the deploy button [#click-the-deploy-button] [![Deploy to Cloudflare Workers](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/shrinathsnayak/cloudflare-experiments/tree/main/apps/experiments/social-preview-inspector) </Step> <Step> ### Configure bindings [#configure-bindings] See `wrangler.json` and the experiment README for required bindings. </Step> <Step> ### Test your deployment [#test-your-deployment] See the experiment README for curl examples. </Step> </Steps> ## Local Development [#local-development] ```bash cd apps/experiments/social-preview-inspector npm install npm run dev ``` ## Configuration [#configuration] No bindings required beyond the Workers runtime. ## Cloudflare Features Used [#cloudflare-features-used] * **[Workers](https://developers.cloudflare.com/workers/)** * **[HTMLRewriter](https://developers.cloudflare.com/workers/runtime-apis/html-rewriter/)** # Speech to Text Transcriber (/docs/experiments/speech-to-text-transcriber) Upload an audio file and receive a transcript using **Workers AI Whisper** (`@cf/openai/whisper-large-v3-turbo`). Validates file size and content type before calling the model. ## Features [#features] * **POST /transcribe** - Multipart upload with field `audio` * **Whisper model** - `@cf/openai/whisper-large-v3-turbo` * **Validation** - Max 2 MB, `audio/*` types only * **Timing** - Returns `durationMs` for the transcription call ## API Reference [#api-reference] ### POST /transcribe [#post-transcribe] Transcribe an uploaded audio file. **`audio`** `file` (required, multipart) Audio file (max 2 MB). Accepted types include `audio/mpeg`, `audio/wav`, `audio/webm`, `audio/ogg`, and other `audio/*` MIME types. #### Example Request [#example-request] ```bash curl -X POST "https://your-worker.workers.dev/transcribe" \ -F "audio=@sample.mp3" ``` #### Success Response [#success-response] ```json { "text": "Hello, this is a test recording.", "language": "en", "durationMs": 842 } ``` #### Error Codes [#error-codes] * `400` - Missing or invalid audio (`INVALID_AUDIO`) * `502` - Transcription failed or empty result (`TRANSCRIBE_ERROR`) ## Use Cases [#use-cases] * Add speech-to-text to edge apps without external API keys * Prototype voice note or meeting transcription workflows * Learn Workers AI audio model integration patterns ## Limitations [#limitations] * Max upload size 2 MB per request (Whisper model limits apply) * No chunking for long recordings; split large files client-side * Requires Workers AI enabled on your Cloudflare account ## Deployment [#deployment] <Steps> <Step> ### Click the deploy button [#click-the-deploy-button] [![Deploy to Cloudflare Workers](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/shrinathsnayak/cloudflare-experiments/tree/main/apps/experiments/speech-to-text-transcriber) </Step> <Step> ### Deploy [#deploy] Enable Workers AI. The `AI` binding is declared in `wrangler.json`. </Step> <Step> ### Test your deployment [#test-your-deployment] ```bash curl -X POST "https://your-worker.workers.dev/transcribe" -F "audio=@sample.mp3" ``` </Step> </Steps> ## Local Development [#local-development] ```bash cd apps/experiments/speech-to-text-transcriber npm install npm run dev ``` ## Configuration [#configuration] | Binding | Purpose | | ------- | ------------------------ | | `AI` | Workers AI Whisper model | ## Cloudflare Features Used [#cloudflare-features-used] * **[Workers](https://developers.cloudflare.com/workers/)** - Edge compute runtime * **[Workers AI](https://developers.cloudflare.com/workers-ai/)** - Whisper speech-to-text # SSL Certificate Inspector (/docs/experiments/ssl-certificate-inspector) Inspect TLS certificate metadata for a domain. Uses **Certificate Transparency** logs (crt.sh) for issuer, subject, validity, SAN list, and days-until-expiry, plus an HTTPS HEAD probe for reachability. ## Features [#features] * **GET /inspect?domain=** - Certificate metadata + reachability * **CT-based cert data** - Issuer, subject, validity window, SANs * **HTTPS probe** - Confirms the domain is reachable over TLS * **Honest limitations** - Documents that full live cert chain inspection is limited in Workers ## API Reference [#api-reference] ### GET /inspect [#get-inspect] Inspect certificate metadata for a hostname. <TypeTable type="{ domain: { description: "Hostname only (no scheme). Example: `cloudflare.com`", type: "string", required: true, }, }" /> #### Example Request [#example-request] ```bash curl "https://your-worker.workers.dev/inspect?domain=cloudflare.com" ``` #### Success Response [#success-response] ```json { "domain": "cloudflare.com", "reachable": true, "tlsVersion": "TLSv1.3", "certificate": { "issuer": "CN=WE1,O=Google Trust Services,C=US", "subject": "CN=cloudflare.com", "notBefore": "2025-01-01T00:00:00", "notAfter": "2026-01-01T00:00:00", "daysUntilExpiry": 180, "san": ["cloudflare.com", "*.cloudflare.com"], "serialNumber": "03:AB:CD..." }, "source": "certificate-transparency", "note": "Certificate metadata comes from Certificate Transparency logs (crt.sh)..." } ``` #### Error Codes [#error-codes] * `400` - Invalid domain (`INVALID_DOMAIN`) * `502` - Lookup or probe failed (`INSPECT_ERROR`) <Callout> Workers cannot perform arbitrary live TLS certificate handshakes for any domain. This experiment uses CT logs for certificate fields and a HEAD request for reachability. For production cert monitoring, consider dedicated TLS inspection tooling. </Callout> ## Use Cases [#use-cases] * Check certificate expiry and SAN coverage from the edge * Learn CT log lookup patterns for domain security audits * Prototype cert monitoring alerts before integrating a full scanner ## Limitations [#limitations] * Certificate data comes from CT logs, not a live handshake * crt.sh availability and freshness affect results * `tlsVersion` on the Worker request reflects the Worker's outbound fetch, not the target's full chain ## Deployment [#deployment] <Steps> <Step> ### Click the deploy button [#click-the-deploy-button] [![Deploy to Cloudflare Workers](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/shrinathsnayak/cloudflare-experiments/tree/main/apps/experiments/ssl-certificate-inspector) </Step> <Step> ### Test your deployment [#test-your-deployment] ```bash curl "https://your-worker.workers.dev/inspect?domain=cloudflare.com" ``` </Step> </Steps> ## Local Development [#local-development] ```bash cd apps/experiments/ssl-certificate-inspector npm install npm run dev ``` ```bash curl "http://localhost:8787/inspect?domain=example.com" ``` ## Cloudflare Features Used [#cloudflare-features-used] * **[Workers](https://developers.cloudflare.com/workers/)** - Edge compute and outbound fetch * **Fetch API** - HTTPS reachability probe # Task Queue (/docs/experiments/task-queue) A minimal reference for **Queues**: HTTP requests enqueue messages via a producer binding; a `queue()` consumer processes them in the background. KV tracks enqueue and process counts. ## API Reference [#api-reference] ### POST /enqueue [#post-enqueue] Enqueues a message for async processing. <TypeTable type="{ message: { description: "Task payload (max 500 characters).", type: "string", required: true, }, }" /> #### Example Request [#example-request] ```bash curl -X POST "https://your-worker.workers.dev/enqueue" \ -H "Content-Type: application/json" \ -d '{"message": "sync analytics"}' ``` #### Example Response [#example-response] ```json { "queued": true, "message": "sync analytics", "enqueuedAt": "2025-06-20T12:00:00.000Z" } ``` #### Error Responses [#error-responses] ```json { "error": "Missing or invalid message (required string, max 500 chars)", "code": "INVALID_MESSAGE" } ``` ### GET /stats [#get-stats] Returns enqueue and process counts. ```bash curl "https://your-worker.workers.dev/stats" ``` ```json { "enqueued": 10, "processed": 8 } ``` <Callout> `processed` may lag behind `enqueued` until the queue consumer runs. This is expected async behavior. </Callout> ## Use Cases [#use-cases] * Learn producer (`TASK_QUEUE.send`) and consumer (`queue()`) setup * Decouple HTTP responses from slow background work * Webhook delivery, email sending, or data pipeline triggers * Building retryable async workflows on Cloudflare ## Limitations [#limitations] * Demo queue with basic stats; no dead-letter queue or idempotency guarantees * Message length is capped * Requires Queue and KV bindings; not a stateless Worker ## Deployment [#deployment] <Steps> <Step> ### Click the deploy button [#click-the-deploy-button] [![Deploy to Cloudflare Workers](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/shrinathsnayak/cloudflare-experiments/tree/main/apps/experiments/task-queue) </Step> <Step> ### Create queue and KV [#create-queue-and-kv] Create the `task-queue` queue and KV namespace in your Cloudflare dashboard. Update `wrangler.json` bindings. </Step> <Step> ### Enqueue and check stats [#enqueue-and-check-stats] ```bash curl -X POST "https://your-worker.workers.dev/enqueue" \ -H "Content-Type: application/json" \ -d '{"message": "hello"}' curl "https://your-worker.workers.dev/stats" ``` </Step> </Steps> ## Local Development [#local-development] ```bash cd apps/experiments/task-queue npm install npm run dev ``` ## Configuration [#configuration] `wrangler.json` declares: * **Queue producer** `TASK_QUEUE` → queue `task-queue` * **Queue consumer** on `task-queue` (batch size 10, 5s timeout) * **KV binding** `STATS` for counters ## Cloudflare Features Used [#cloudflare-features-used] * **[Workers](https://developers.cloudflare.com/workers/)** - Edge compute runtime * **[Queues](https://developers.cloudflare.com/queues/)** - async message processing * **[Workers KV](https://developers.cloudflare.com/kv/)** - enqueue/process statistics # Text Similarity (/docs/experiments/text-similarity) Compare two text strings for semantic similarity using [Workers AI](https://developers.cloudflare.com/workers-ai/) embeddings and cosine similarity. Returns a score between 0 and 1. ## Features [#features] * Compare two text strings for semantic similarity (score 0–1) * Workers AI embeddings via `@cf/baai/bge-base-en-v1.5` * Cosine similarity computed at the edge * Stateless GET API with `text1` and `text2` query parameters ## API Reference [#api-reference] ### GET /similarity [#get-similarity] Computes the cosine similarity between embeddings of two text inputs. <TypeTable type="{ text1: { description: "First text to compare (max 10,000 characters)", type: "string", required: true, }, text2: { description: "Second text to compare (max 10,000 characters)", type: "string", required: true, }, }" /> #### Example Request [#example-request] ```bash curl "https://your-worker.workers.dev/similarity?text1=hello%20world&text2=hello%20there" ``` #### Success Response [#success-response] **`text1`** `string` The first input text **`text2`** `string` The second input text **`similarity`** `number` Cosine similarity score between 0 and 1 (higher means more similar) ```json { "text1": "hello world", "text2": "hello there", "similarity": 0.82 } ``` #### Error Response [#error-response] ```json { "error": "Missing or invalid query parameter: text1", "code": "INVALID_TEXT1" } ``` #### Error Codes [#error-codes] * `400` - Missing or invalid `text1` (`INVALID_TEXT1`) * `400` - Missing or invalid `text2` (`INVALID_TEXT2`) * `502` - Workers AI model run failed (`AI_ERROR`) ## Use Cases [#use-cases] * Detect duplicate or near-duplicate content at the edge * Rank search results or FAQ matches by semantic relevance * Learn Workers AI embedding models and vector similarity * Build lightweight deduplication or matching APIs without a vector database ## Limitations [#limitations] * Workers AI is subject to [usage limits](https://developers.cloudflare.com/workers-ai/platform/limits/) by plan * Each input string length is capped * Compares two strings only; no corpus or vector database search ## Deployment [#deployment] <Steps> <Step> ### Click the deploy button [#click-the-deploy-button] [![Deploy to Cloudflare Workers](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/shrinathsnayak/cloudflare-experiments/tree/main/apps/experiments/text-similarity) </Step> <Step> ### Deploy [#deploy] Enable the **Workers AI** binding (`AI`) in your Worker settings. The deploy button configures this automatically via `wrangler.json`. Requires a Cloudflare account with Workers AI enabled. </Step> <Step> ### Test your deployment [#test-your-deployment] ```bash curl "https://your-worker.workers.dev/similarity?text1=hello&text2=hi" ``` </Step> </Steps> ## Local Development [#local-development] ```bash cd apps/experiments/text-similarity npm install npm run dev ``` Test locally: ```bash curl "http://localhost:8787/similarity?text1=hello&text2=hi" ``` ## Cloudflare Features Used [#cloudflare-features-used] * **[Workers](https://developers.cloudflare.com/workers/)** - Edge compute runtime * **[Workers AI](https://developers.cloudflare.com/workers-ai/)** - `@cf/baai/bge-base-en-v1.5` text embedding model # Text Translator (/docs/experiments/text-translator) Translate text between languages using [Workers AI](https://developers.cloudflare.com/workers-ai/) and Meta's m2m100 model. Send text via query parameters and receive a translation without external API keys. ## Features [#features] * Translate text between languages using Workers AI * Machine translation model (`@cf/meta/m2m100-1.2b`) at the edge * Stateless GET API with source text and target language * No third-party translation service required ## API Reference [#api-reference] ### GET /translate [#get-translate] Translates the provided text from a source language to a target language. <TypeTable type="{ text: { description: "Text to translate (max 10,000 characters)", type: "string", required: true, }, target: { description: "Target language code (e.g. `es`, `fr`, `de`)", type: "string", required: true, }, source: { description: "Source language code (default: `en`)", type: "string", }, }" /> #### Example Request [#example-request] ```bash curl "https://your-worker.workers.dev/translate?text=hello&target=es&source=en" ``` #### Success Response [#success-response] **`text`** `string` The original input text **`source`** `string` Source language code used for translation **`target`** `string` Target language code used for translation **`translation`** `string` The translated text ```json { "text": "hello", "source": "en", "target": "es", "translation": "hola" } ``` #### Error Response [#error-response] ```json { "error": "Missing or invalid query parameter: text", "code": "INVALID_TEXT" } ``` #### Error Codes [#error-codes] * `400` - Missing or invalid `text` (`INVALID_TEXT`) * `400` - Missing or invalid `target` (`INVALID_TARGET`) * `400` - Invalid `source` language code (`INVALID_SOURCE`) * `502` - Workers AI model run failed (`AI_ERROR`) ## Use Cases [#use-cases] * Add on-the-fly translation to edge APIs and proxies * Localize user-facing strings without a third-party translation service * Learn Workers AI text-to-text models in a stateless Worker * Prototype multilingual chatbots and content tools at the edge ## Limitations [#limitations] * Workers AI is subject to [usage limits](https://developers.cloudflare.com/workers-ai/platform/limits/) by plan * Input text length is capped * Single-string translation; no document or batch mode ## Deployment [#deployment] <Steps> <Step> ### Click the deploy button [#click-the-deploy-button] [![Deploy to Cloudflare Workers](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/shrinathsnayak/cloudflare-experiments/tree/main/apps/experiments/text-translator) </Step> <Step> ### Deploy [#deploy] Enable the **Workers AI** binding (`AI`) in your Worker settings. The deploy button configures this automatically via `wrangler.json`. Requires a Cloudflare account with Workers AI enabled. </Step> <Step> ### Test your deployment [#test-your-deployment] ```bash curl "https://your-worker.workers.dev/translate?text=hello&target=es&source=en" ``` </Step> </Steps> ## Local Development [#local-development] ```bash cd apps/experiments/text-translator npm install npm run dev ``` Test locally: ```bash curl "http://localhost:8787/translate?text=hello&target=es&source=en" ``` ## Cloudflare Features Used [#cloudflare-features-used] * **[Workers](https://developers.cloudflare.com/workers/)** - Edge compute runtime * **[Workers AI](https://developers.cloudflare.com/workers-ai/)** - `@cf/meta/m2m100-1.2b` machine translation model # Turnstile Verify (/docs/experiments/turnstile-verify) Verify [Cloudflare Turnstile](https://developers.cloudflare.com/turnstile/) tokens server-side by calling the siteverify API from a Worker. This experiment uses the `TURNSTILE_SECRET_KEY` environment secret - there is no Turnstile Worker binding. ## API Reference [#api-reference] ### POST /verify [#post-verify] Validates a client Turnstile token against the Cloudflare siteverify endpoint and returns the verification result. <TypeTable type="{ token: { description: "Turnstile response token from the client widget", type: "string", required: true, }, }" /> #### Example Request [#example-request] ```bash curl -X POST "https://your-worker.workers.dev/verify" \ -H "Content-Type: application/json" \ -d '{"token":"0.abc123..."}' ``` #### Success Response [#success-response] **`success`** `boolean` Whether Turnstile accepted the token **`hostname`** `string` (optional) Hostname associated with the token, when provided by siteverify **`action`** `string` (optional) Custom action name from the widget, when provided by siteverify **`errorCodes`** `string[]` (optional) Present when `success` is `false`; Turnstile error codes from siteverify ```json { "success": true, "hostname": "example.com", "action": "login" } ``` When verification fails: ```json { "success": false, "errorCodes": ["invalid-input-response"] } ``` #### Error Response [#error-response] ```json { "error": "Missing or invalid field: token", "code": "INVALID_TOKEN" } ``` #### Error Codes [#error-codes] * `400` - Invalid JSON body (`INVALID_BODY`) * `400` - Missing or invalid `token` (`INVALID_TOKEN`) * `502` - Turnstile secret key is not configured (`MISSING_SECRET`) * `502` - Siteverify request failed (`VERIFY_ERROR`) ## Use Cases [#use-cases] * Validate Turnstile tokens on form submission before processing requests * Protect login, signup, or contact endpoints from bots at the edge * Learn server-side Turnstile verification without a backend framework * Return structured siteverify results to your frontend or API gateway ## Limitations [#limitations] * Requires a `TURNSTILE_SECRET_KEY` secret configured in the Worker * Server-side verification only; does not render the Turnstile widget * Turnstile tokens are single-use and expire quickly ## Deployment [#deployment] <Steps> <Step> ### Click the deploy button [#click-the-deploy-button] [![Deploy to Cloudflare Workers](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/shrinathsnayak/cloudflare-experiments/tree/main/apps/experiments/turnstile-verify) </Step> <Step> ### Set the Turnstile secret [#set-the-turnstile-secret] After deploy, set your Turnstile **secret key** as a Worker secret: ```bash cd apps/experiments/turnstile-verify npx wrangler secret put TURNSTILE_SECRET_KEY ``` Use the secret key from your Turnstile widget configuration in the Cloudflare dashboard. </Step> <Step> ### Test your deployment [#test-your-deployment] ```bash curl -X POST "https://your-worker.workers.dev/verify" \ -H "Content-Type: application/json" \ -d '{"token":"YOUR_TURNSTILE_TOKEN"}' ``` </Step> </Steps> ## Local Development [#local-development] ```bash cd apps/experiments/turnstile-verify npm install npx wrangler secret put TURNSTILE_SECRET_KEY npm run dev ``` Test locally: ```bash curl -X POST "http://localhost:8787/verify" \ -H "Content-Type: application/json" \ -d '{"token":"YOUR_TURNSTILE_TOKEN"}' ``` ## Cloudflare Features Used [#cloudflare-features-used] * **[Workers](https://developers.cloudflare.com/workers/)** - Edge compute runtime * **[Cloudflare Turnstile](https://developers.cloudflare.com/turnstile/)** - siteverify API for token validation # Uptime Monitor Alerts (/docs/experiments/uptime-monitor-alerts) Extend the is-it-down pattern into a persistent monitor. **Cron** pings registered URLs, logs checks to **D1**, and sends **email alerts** when a URL transitions from up to down. ## Features [#features] * POST /monitors - register URL + alert email * DELETE /monitors/:id - remove monitor * GET /monitors/:id/history - check history and uptime percentage * Scheduled checks every 5 minutes ## API Reference [#api-reference] ### POST /monitors [#post-monitors] ```json { "url": "https://example.com", "alertEmail": "you@example.com" } ``` ### DELETE /monitors/:id [#delete-monitorsid] Remove a monitor. ### GET /monitors/:id/history [#get-monitorsidhistory] Returns check history and uptime percentage. #### Error Codes [#error-codes] * `400` - `INVALID_URL`, `INVALID_EMAIL`, `INVALID_BODY` * `404` - `NOT_FOUND` ## Use Cases [#use-cases] * Lightweight uptime monitoring for side projects * Learn Cron + D1 + send\_email alert patterns on Workers * Track uptime percentage over time for SLA reporting ## Limitations [#limitations] * Requires D1, Cron, and send\_email bindings configured for your domain * 5-minute check interval; not sub-minute alerting * Single-region edge checks; not global multi-PoP consensus ## Deployment [#deployment] <Steps> <Step> ### Click the deploy button [#click-the-deploy-button] [![Deploy to Cloudflare Workers](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/shrinathsnayak/cloudflare-experiments/tree/main/apps/experiments/uptime-monitor-alerts) </Step> <Step> ### Configure bindings [#configure-bindings] D1 `DB`, `send_email` binding `EMAILER`, cron `*/5 * * * *`. Configure allowed sender/destination addresses in wrangler. </Step> <Step> ### Test your deployment [#test-your-deployment] See the experiment README for curl examples. </Step> </Steps> ## Local Development [#local-development] ```bash cd apps/experiments/uptime-monitor-alerts npm install npm run dev ``` ## Configuration [#configuration] D1 `DB`, `send_email` binding `EMAILER`, cron `*/5 * * * *`. Configure allowed sender/destination addresses in wrangler. ## Cloudflare Features Used [#cloudflare-features-used] * **[D1](https://developers.cloudflare.com/d1/)** * **[Cron Triggers](https://developers.cloudflare.com/workers/configuration/cron-triggers/)** * **[Email Routing / send\_email](https://developers.cloudflare.com/email-routing/email-workers/send-email-workers/)** # URL DNS Lookup (/docs/experiments/url-dns-lookup) Retrieve all DNS record types for any URL's hostname using Cloudflare's DNS over HTTPS (DoH) API. ## API Reference [#api-reference] ### GET /dns [#get-dns] Extracts the hostname from the provided URL and returns all available DNS records. <TypeTable type="{ url: { description: "Any http or https URL. The hostname will be extracted and looked up.", type: "string", required: true, }, }" /> #### Example Request [#example-request] ```bash curl "https://your-worker.workers.dev/dns?url=https://www.cloudflare.com/page" ``` #### Success Response [#success-response] **`hostname`** `string` The extracted hostname that was looked up **`records`** `object` DNS records grouped by type. Only record types that exist for the hostname are included. **`A`** `array` IPv4 address records **`AAAA`** `array` IPv6 address records **`CNAME`** `array` Canonical name records **`MX`** `array` Mail exchange records **`NS`** `array` Name server records **`TXT`** `array` Text records **`SOA`** `array` Start of authority records **`CAA`** `array` Certification authority authorization records Each DNS record contains: **`name`** `string` The domain name for this record **`type`** `string` The DNS record type (A, AAAA, CNAME, etc.) **`ttl`** `number` Time to live in seconds **`data`** `string` The record data (IP address, hostname, etc.) ```json { "hostname": "example.com", "records": { "A": [ { "name": "example.com", "type": "A", "ttl": 1726, "data": "93.184.220.34" } ], "AAAA": [ { "name": "example.com", "type": "AAAA", "ttl": 1726, "data": "2606:2800:220:1:248:1893:25c8:1946" } ], "NS": [ { "name": "example.com", "type": "NS", "ttl": 172800, "data": "a.iana-servers.net." } ] } } ``` #### Error Response [#error-response] ```json { "error": "Missing or invalid query parameter: url", "code": "INVALID_URL" } ``` #### Error Codes [#error-codes] * `400` - Missing or invalid `url` parameter (not http/https) * `502` - DNS lookup failed (DoH timeout or error) ## Supported Record Types [#supported-record-types] The API queries for the following DNS record types: * **A** - IPv4 addresses * **AAAA** - IPv6 addresses * **CNAME** - Canonical names * **MX** - Mail exchange servers * **NS** - Name servers * **TXT** - Text records * **SOA** - Start of authority * **CAA** - Certificate authority authorization ## Implementation Details [#implementation-details] * Uses Cloudflare's public DNS over HTTPS (DoH) API * Queries all record types in parallel for fast responses * Stateless - no database or KV storage required * Timeout protection on DoH requests ## Use Cases [#use-cases] * Resolve DNS records for the hostname extracted from any URL * Debug DNS configuration during deployments or migrations * Build diagnostic tools without running `dig` locally * Verify mail (MX), certificate (CAA), or CDN (CNAME) records from the edge ## Limitations [#limitations] * Uses the hostname only; URL path and query string are ignored * Queries a fixed set of record types; no PTR or SRV in this experiment * DoH timeout if upstream DNS is slow or unreachable * Results reflect public DNS; may differ from resolver-specific or split-horizon views ## Deployment [#deployment] <Steps> <Step> ### Click the deploy button [#click-the-deploy-button] [![Deploy to Cloudflare Workers](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/shrinathsnayak/cloudflare-experiments/tree/main/apps/experiments/url-dns-lookup) </Step> <Step> ### Deploy [#deploy] Follow the deployment wizard to deploy the Worker to your Cloudflare account. No additional configuration or bindings required. </Step> <Step> ### Test your deployment [#test-your-deployment] ```bash curl "https://your-worker.workers.dev/dns?url=https://www.cloudflare.com" ``` </Step> </Steps> ## Local Development [#local-development] ```bash cd apps/experiments/url-dns-lookup npm install npm run dev ``` Test locally: ```bash curl "http://localhost:8787/dns?url=https://www.cloudflare.com" ``` ## Cloudflare Features Used [#cloudflare-features-used] * **[Workers](https://developers.cloudflare.com/workers/)** - Edge compute runtime * **[Fetch API](https://developers.cloudflare.com/workers/runtime-apis/fetch/)** - HTTP requests to Cloudflare DoH * **[Edge network](https://developers.cloudflare.com/workers/reference/how-workers-works/)** - Low-latency DNS lookups # Vectorize Search (/docs/experiments/vectorize-search) Upsert text documents as vectors and search them by natural language using [Workers AI](https://developers.cloudflare.com/workers-ai/) embeddings and [Vectorize](https://developers.cloudflare.com/vectorize/) at the edge. ## API Reference [#api-reference] ### POST /upsert [#post-upsert] Embeds text with Workers AI and upserts the vector into Vectorize with metadata. <TypeTable type="{ id: { description: "Unique document identifier (non-empty string)", type: "string", required: true, }, text: { description: "Text to embed and store (max 10,000 characters)", type: "string", required: true, }, }" /> #### Example Request [#example-request] ```bash curl -X POST "https://your-worker.workers.dev/upsert" \ -H "Content-Type: application/json" \ -d '{"id":"doc-1","text":"Cloudflare Workers run at the edge"}' ``` #### Success Response [#success-response] **`id`** `string` The document id that was upserted ```json { "id": "doc-1" } ``` #### Error Response [#error-response] ```json { "error": "Missing or invalid field: text", "code": "INVALID_TEXT" } ``` #### Error Codes [#error-codes] * `400` - Invalid JSON body (`INVALID_BODY`) * `400` - Missing or invalid `id` (`INVALID_ID`) * `400` - Missing or invalid `text` (`INVALID_TEXT`) * `502` - Embedding or Vectorize operation failed (`VECTORIZE_ERROR`) ### GET /search [#get-search] Embeds the query with Workers AI and returns the nearest Vectorize matches. <TypeTable type="{ q: { description: "Search query (max 10,000 characters)", type: "string", required: true, }, topK: { description: "Number of results to return (1–20, default 5)", type: "number", }, }" /> #### Example Request [#example-request-1] ```bash curl "https://your-worker.workers.dev/search?q=edge%20computing&topK=5" ``` #### Success Response [#success-response-1] **`query`** `string` The search query that was embedded **`results`** `array` Matching documents, each with `id`, `score`, and `text` ```json { "query": "edge computing", "results": [ { "id": "doc-1", "score": 0.87, "text": "Cloudflare Workers run at the edge" } ] } ``` #### Error Response [#error-response-1] ```json { "error": "Missing or invalid query parameter: q", "code": "INVALID_QUERY" } ``` #### Error Codes [#error-codes-1] * `400` - Missing or invalid `q` (`INVALID_QUERY`) * `400` - Invalid `topK` (must be 1–20) (`INVALID_TOP_K`) * `502` - Embedding or Vectorize operation failed (`VECTORIZE_ERROR`) ## Use Cases [#use-cases] * Build semantic search over product docs or support articles at the edge * Prototype RAG pipelines with Vectorize as the vector store * Learn Workers AI embeddings with `@cf/baai/bge-base-en-v1.5` * Compare cosine similarity search without running a separate vector database ## Limitations [#limitations] * Demo index; vectors are tied to your Vectorize binding and redeploy lifecycle * Text length is capped before embedding * Requires Workers AI and Vectorize bindings * Semantic search over the demo corpus only; not a production RAG pipeline ## Deployment [#deployment] <Steps> <Step> ### Click the deploy button [#click-the-deploy-button] [![Deploy to Cloudflare Workers](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/shrinathsnayak/cloudflare-experiments/tree/main/apps/experiments/vectorize-search) </Step> <Step> ### Create the Vectorize index [#create-the-vectorize-index] Before deploying, create the index (768 dimensions, cosine metric for the embedding model): ```bash wrangler vectorize create experiment-search --dimensions=768 --metric=cosine ``` The index name must match `experiment-search` in `wrangler.json`. </Step> <Step> ### Deploy [#deploy] Enable the **Workers AI** binding (`AI`) and **Vectorize** binding (`VECTORIZE`). The deploy button configures these via `wrangler.json`. Requires a Cloudflare account with Workers AI and Vectorize enabled. </Step> <Step> ### Test your deployment [#test-your-deployment] ```bash curl -X POST "https://your-worker.workers.dev/upsert" \ -H "Content-Type: application/json" \ -d '{"id":"doc-1","text":"Cloudflare Workers run at the edge"}' curl "https://your-worker.workers.dev/search?q=edge%20computing&topK=5" ``` </Step> </Steps> ## Local Development [#local-development] ```bash cd apps/experiments/vectorize-search npm install wrangler vectorize create experiment-search --dimensions=768 --metric=cosine npm run dev ``` Test locally: ```bash curl -X POST "http://localhost:8787/upsert" \ -H "Content-Type: application/json" \ -d '{"id":"doc-1","text":"Cloudflare Workers run at the edge"}' curl "http://localhost:8787/search?q=edge%20computing&topK=5" ``` ## Cloudflare Features Used [#cloudflare-features-used] * **[Workers](https://developers.cloudflare.com/workers/)** - Edge compute runtime * **[Workers AI](https://developers.cloudflare.com/workers-ai/)** - `@cf/baai/bge-base-en-v1.5` text embeddings * **[Vectorize](https://developers.cloudflare.com/vectorize/)** - `experiment-search` index (768 dims, cosine) # Webhook Relay Inspector (/docs/experiments/webhook-relay-inspector) Create a unique inbound URL per session with a **Durable Object**. Any HTTP request to that URL is captured (method, headers, body, timestamp) and can be listed or inspected for webhook debugging. ## Features [#features] * POST /relay/new - create a capture session * ALL /relay/:id - capture inbound requests * GET /relay/:id/requests - list captures * GET /relay/:id/requests/:requestId - full request details ## API Reference [#api-reference] ### POST /relay/new [#post-relaynew] Create a new relay session. #### Success Response [#success-response] ```json { "id": "abc123", "inboundUrl": "https://your-worker.workers.dev/relay/abc123" } ``` ### ALL /relay/:id [#all-relayid] Captures any inbound HTTP request to the session. ### GET /relay/:id/requests [#get-relayidrequests] List captured requests (summary). ### GET /relay/:id/requests/:requestId [#get-relayidrequestsrequestid] Full request details including headers and body. #### Error Codes [#error-codes] * `404` - `NOT_FOUND` * `400` - `INVALID_ID` ## Use Cases [#use-cases] * Debug Stripe, GitHub, or custom webhook payloads during integration * Share a temporary inbound URL with a third party for testing * Inspect headers and raw body without deploying a full receiver ## Limitations [#limitations] * Requires Durable Objects binding and migration * Sessions are not authenticated; anyone with the URL can post * Storage is bounded by DO limits; not long-term log retention ## Deployment [#deployment] <Steps> <Step> ### Click the deploy button [#click-the-deploy-button] [![Deploy to Cloudflare Workers](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/shrinathsnayak/cloudflare-experiments/tree/main/apps/experiments/webhook-relay-inspector) </Step> <Step> ### Configure bindings [#configure-bindings] Durable Object `RELAY` / `WebhookRelay` with migration in `wrangler.json`. </Step> <Step> ### Test your deployment [#test-your-deployment] See the experiment README for curl examples. </Step> </Steps> ## Local Development [#local-development] ```bash cd apps/experiments/webhook-relay-inspector npm install npm run dev ``` ## Configuration [#configuration] Durable Object `RELAY` / `WebhookRelay` with migration in `wrangler.json`. ## Cloudflare Features Used [#cloudflare-features-used] * **[Durable Objects](https://developers.cloudflare.com/durable-objects/)** * **[Workers](https://developers.cloudflare.com/workers/)** # Webhook Signature Verifier (/docs/experiments/webhook-signature-verifier) Accept a raw payload, secret, signature header value, and algorithm. Computes the expected HMAC (Stripe/GitHub `sha256=` hex style) and returns whether it matches using a **timing-safe compare**. ## Features [#features] * POST /verify - HMAC-SHA256 verification with clear comparison explanation * Supports prefixed signatures (`sha256=...`) and raw hex * Uses Web Crypto at the edge; no external libraries ## API Reference [#api-reference] ### POST /verify [#post-verify] **`payload`** `string` (required) - Raw request body as received. **`secret`** `string` (required) - Shared signing secret. **`signature`** `string` (required) - Signature from the webhook header. **`algorithm`** `string` (optional) - Default `sha256`. #### Example Request [#example-request] ```bash curl -X POST "https://your-worker.workers.dev/verify" \ -H "Content-Type: application/json" \ -d '{"payload":"{\"id\":1}","secret":"whsec_test","signature":"sha256=..."}' ``` #### Success Response [#success-response] ```json { "match": true, "algorithm": "sha256", "expectedSignature": "sha256=...", "providedSignature": "sha256=...", "explanation": "Timing-safe comparison of 32-byte HMAC digests returned match." } ``` #### Error Codes [#error-codes] * `400` - `INVALID_BODY`, `MISSING_FIELD`, `INVALID_ALGORITHM` ## Use Cases [#use-cases] * Debug Stripe, GitHub, or Shopify webhook signature mismatches * Learn timing-safe HMAC verification patterns for Workers * Validate signing logic before wiring production webhook handlers ## Limitations [#limitations] * HMAC-SHA256 only in this experiment * Secrets are sent in the request body; use only for debugging, not production traffic * No replay protection or timestamp validation ## Deployment [#deployment] <Steps> <Step> ### Click the deploy button [#click-the-deploy-button] [![Deploy to Cloudflare Workers](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/shrinathsnayak/cloudflare-experiments/tree/main/apps/experiments/webhook-signature-verifier) </Step> <Step> ### Configure bindings [#configure-bindings] See `wrangler.json` and the experiment README for required bindings. </Step> <Step> ### Test your deployment [#test-your-deployment] See the experiment README for curl examples. </Step> </Steps> ## Local Development [#local-development] ```bash cd apps/experiments/webhook-signature-verifier npm install npm run dev ``` ## Configuration [#configuration] No bindings required beyond the Workers runtime. ## Cloudflare Features Used [#cloudflare-features-used] * **[Workers](https://developers.cloudflare.com/workers/)** * **[Web Crypto](https://developers.cloudflare.com/workers/runtime-apis/web-crypto/)** # Website Change Tracker (/docs/experiments/website-change-tracker) Register URLs for on-demand and **Cron**-scheduled snapshots. Rendered text is stored in **R2**, content hashes and diff summaries in **D1**. View change history per tracked URL. ## Features [#features] * POST /track - register a URL * DELETE /track?url= - unregister * GET /history?url= - snapshot diff history * Scheduled handler every 30 minutes for tracked URLs ## API Reference [#api-reference] ### POST /track [#post-track] Register a URL for scheduled tracking. ```json { "url": "https://example.com" } ``` ### DELETE /track?url= [#delete-trackurl] Unregister a tracked URL. ### GET /history?url= [#get-historyurl] Return snapshot history with content hashes and diff summaries. #### Error Codes [#error-codes] * `400` - `INVALID_URL`, `INVALID_BODY` * `404` - `NOT_TRACKED` ## Use Cases [#use-cases] * Monitor marketing or legal pages for unexpected content changes * Learn R2 + D1 + Cron + Browser Rendering together * Prototype change-detection alerts before production hardening ## Limitations [#limitations] * Requires D1, R2, Browser Rendering, and Cron bindings * Diff summary is line-based heuristic, not semantic * 30-minute cron interval; not real-time ## Deployment [#deployment] <Steps> <Step> ### Click the deploy button [#click-the-deploy-button] [![Deploy to Cloudflare Workers](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/shrinathsnayak/cloudflare-experiments/tree/main/apps/experiments/website-change-tracker) </Step> <Step> ### Configure bindings [#configure-bindings] D1 `DB`, R2 `SNAPSHOTS`, Browser `BROWSER`, cron `*/30 * * * *`. Run D1 migrations before deploy. </Step> <Step> ### Test your deployment [#test-your-deployment] See the experiment README for curl examples. </Step> </Steps> ## Local Development [#local-development] ```bash cd apps/experiments/website-change-tracker npm install npm run dev ``` ## Configuration [#configuration] D1 `DB`, R2 `SNAPSHOTS`, Browser `BROWSER`, cron `*/30 * * * *`. Run D1 migrations before deploy. ## Cloudflare Features Used [#cloudflare-features-used] * **[Browser Rendering](https://developers.cloudflare.com/browser-rendering/)** * **[R2](https://developers.cloudflare.com/r2/)** * **[D1](https://developers.cloudflare.com/d1/)** * **[Cron Triggers](https://developers.cloudflare.com/workers/configuration/cron-triggers/)** # Website DevTools Inspector (/docs/experiments/website-devtools-inspector) Inspect any website with a comprehensive analysis similar to browser DevTools. Get HTTP headers, cookies, response time, metadata, and all loaded resources including scripts, stylesheets, images, and links. ## API Reference [#api-reference] ### GET /devtools [#get-devtools] Perform a comprehensive inspection of a webpage by providing its URL. <TypeTable type="{ url: { description: "The URL of the webpage to inspect. Must be a valid HTTP or HTTPS URL.", type: "string", required: true, }, }" /> #### Example Request [#example-request] ```bash curl "https://your-worker.workers.dev/devtools?url=https://www.cloudflare.com" ``` #### Response Structure [#response-structure] **`success`** `boolean` Indicates if the request was successful **`data`** `object` The comprehensive inspection results **`data.url`** `string` The requested URL **`data.statusCode`** `number` HTTP response status code (e.g., 200, 404, 500) **`data.responseTimeMs`** `number` Response time in milliseconds (rounded) **`data.headers`** `object` HTTP response headers as key-value pairs. All header names are lowercase. **`data.cookies`** `string[]` Array of Set-Cookie header values **`data.metadata`** `object` Page metadata **`data.metadata.title`** `string | null` Page title from `<title>` tag **`data.metadata.description`** `string | null` Meta description **`data.metadata.canonical`** `string | null` Canonical URL if specified **`data.scripts`** `string[]` Array of unique absolute URLs of JavaScript files loaded via `<script src="...">` **`data.stylesheets`** `string[]` Array of unique absolute URLs of CSS files loaded via `<link rel="stylesheet">` **`data.images`** `string[]` Array of unique absolute URLs of images from `<img src="...">` **`data.links`** `string[]` Array of unique absolute URLs from `<a href="...">`. Excludes anchor links (#) and javascript: links. #### Example Response [#example-response] ```json { "success": true, "data": { "url": "https://www.cloudflare.com", "statusCode": 200, "responseTimeMs": 245, "headers": { "content-type": "text/html; charset=utf-8", "server": "cloudflare", "cf-ray": "8a1b2c3d4e5f6g7h-SJC", "cache-control": "max-age=3600", "content-encoding": "gzip", "vary": "Accept-Encoding" }, "cookies": ["__cflb=0123456789abcdef; Path=/; HttpOnly; Secure; SameSite=Lax"], "metadata": { "title": "Cloudflare - The Web Performance & Security Company", "description": "Here at Cloudflare, we make the Internet work the way it should.", "canonical": "https://www.cloudflare.com/" }, "scripts": [ "https://www.cloudflare.com/js/main.js", "https://ajax.cloudflare.com/cdn-cgi/scripts/analytics.js" ], "stylesheets": [ "https://www.cloudflare.com/css/main.css", "https://www.cloudflare.com/css/responsive.css" ], "images": [ "https://www.cloudflare.com/img/logo.svg", "https://www.cloudflare.com/img/hero-image.png" ], "links": [ "https://www.cloudflare.com/products/", "https://www.cloudflare.com/plans/", "https://developers.cloudflare.com/" ] } } ``` ## Error Responses [#error-responses] ### Invalid URL [#invalid-url] ```json { "success": false, "error": "Missing or invalid query parameter: url", "code": "INVALID_URL" } ``` ### Fetch Error [#fetch-error] ```json { "success": false, "error": "HTTP 404", "code": "FETCH_ERROR" } ``` ## Technical Details [#technical-details] * Built with [Hono](https://hono.dev/) framework * Runs on Cloudflare Workers * Custom User-Agent: `Cloudflare-Experiments-DevtoolsInspector/1.0` * Configurable fetch timeout * Maximum HTML size limit to prevent memory issues * All relative URLs resolved to absolute URLs * Response time measured from request start to completion ## Inspection Details [#inspection-details] ### Headers [#headers] * All response headers are captured * Header names are normalized to lowercase * Includes Cloudflare-specific headers (cf-ray, cf-cache-status, etc.) ### Cookies [#cookies] * Extracted from Set-Cookie response headers * Preserves all cookie attributes (Path, HttpOnly, Secure, SameSite) * Multiple cookies are returned as separate array entries ### Resources [#resources] * Scripts: Extracted from `<script src="...">` * Stylesheets: Extracted from `<link rel="stylesheet" href="...">` * Images: Extracted from `<img src="...">` * Links: Extracted from `<a href="...">` * All URLs are deduplicated and resolved to absolute URLs ### Response Time [#response-time] * Measured in milliseconds from request initiation to response completion * Includes network latency and initial content download * Rounded to the nearest millisecond ## Use Cases [#use-cases] * **Performance Monitoring**: Track response times and identify slow-loading resources * **Security Audits**: Analyze headers, cookies, and security configurations * **Asset Analysis**: Discover all external dependencies and third-party scripts * **SEO Audits**: Inspect metadata and canonical URLs * **Debugging**: Investigate website structure and loaded resources * **Competitive Analysis**: Understand technology stack and external services used * **Compliance Checks**: Verify cookie policies and header configurations ## Limitations [#limitations] * Static HTML fetch for most fields; not a live browser DevTools session * HTML body is capped at 1MB before parsing * Cannot inspect authenticated or paywalled pages * No runtime network waterfall, console, or step-through debugging ## Deployment [#deployment] <Steps> <Step> ### Click the deploy button [#click-the-deploy-button] [![Deploy to Cloudflare Workers](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/shrinathsnayak/cloudflare-experiments/tree/main/apps/experiments/website-devtools-inspector) </Step> <Step> ### Deploy [#deploy] No additional configuration required. </Step> <Step> ### Test your deployment [#test-your-deployment] ```bash curl "https://your-worker.workers.dev/devtools?url=https://example.com" ``` </Step> </Steps> ## Local Development [#local-development] ```bash cd apps/experiments/website-devtools-inspector npm install npm run dev ``` Test locally: ```bash curl "http://localhost:8787/devtools?url=https://example.com" ``` ## Cloudflare Features Used [#cloudflare-features-used] * **[Workers](https://developers.cloudflare.com/workers/)** - Edge compute runtime * **[Fetch API](https://developers.cloudflare.com/workers/runtime-apis/fetch/)** - HTTP requests for headers, HTML, and timing data # Website Metadata Extractor (/docs/experiments/website-metadata-extractor) Extract comprehensive metadata from any webpage including title, description, Open Graph properties, and canonical URL. Perfect for building link previews, SEO analysis tools, or content aggregators. ## API Reference [#api-reference] ### GET /metadata [#get-metadata] Extract metadata from a webpage by providing its URL. <TypeTable type="{ url: { description: "The URL of the webpage to extract metadata from. Must be a valid HTTP or HTTPS URL.", type: "string", required: true, }, }" /> #### Example Request [#example-request] ```bash curl "https://your-worker.workers.dev/metadata?url=https://www.cloudflare.com" ``` #### Response Structure [#response-structure] **`success`** `boolean` Indicates if the request was successful **`data`** `object` The extracted metadata from the webpage **`data.title`** `string | null` The page title extracted from the `<title>` tag **`data.description`** `string | null` The page description from meta tags or Open Graph description **`data.canonical`** `string | null` The canonical URL if specified in the page **`data.og`** `object` Open Graph metadata properties **`data.og.title`** `string` (optional) Open Graph title (og:title) **`data.og.description`** `string` (optional) Open Graph description (og:description) **`data.og.image`** `string` (optional) Open Graph image URL (og:image) **`data.og.type`** `string` (optional) Open Graph content type (og:type) **`data.og.url`** `string` (optional) Open Graph canonical URL (og:url) **`data.og.siteName`** `string` (optional) Open Graph site name (og:site\_name) #### Example Response [#example-response] ```json { "success": true, "data": { "title": "Cloudflare - The Web Performance & Security Company", "description": "Here at Cloudflare, we make the Internet work the way it should.", "canonical": "https://www.cloudflare.com/", "og": { "title": "Cloudflare", "description": "Here at Cloudflare, we make the Internet work the way it should.", "image": "https://www.cloudflare.com/img/cf-facebook-card.png", "type": "website", "url": "https://www.cloudflare.com/", "siteName": "Cloudflare" } } } ``` ## Error Responses [#error-responses] ### Invalid URL [#invalid-url] ```json { "success": false, "error": "Missing or invalid query parameter: url", "code": "INVALID_URL" } ``` ### Fetch Error [#fetch-error] ```json { "success": false, "error": "HTTP 404", "code": "FETCH_ERROR" } ``` ## Technical Details [#technical-details] * Built with [Hono](https://hono.dev/) framework * Runs on Cloudflare Workers * HTML parsing with regex-based extraction * Fetches and processes pages up to the configured size limit * Returns structured JSON with comprehensive metadata ## Use Cases [#use-cases] * **Link Previews**: Generate rich previews for shared links in chat applications * **SEO Analysis**: Audit metadata across multiple pages * **Content Aggregation**: Collect and display metadata from external sources * **Social Media Tools**: Extract Open Graph data for social sharing optimization * **Bookmark Managers**: Automatically fetch metadata when saving URLs ## Limitations [#limitations] * Static HTML only; tags set by client-side JavaScript may be missing * Fetch timeout and HTML size limits on upstream pages * Single URL per request; no batch extraction ## Deployment [#deployment] <Steps> <Step> ### Click the deploy button [#click-the-deploy-button] [![Deploy to Cloudflare Workers](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/shrinathsnayak/cloudflare-experiments/tree/main/apps/experiments/website-metadata-extractor) </Step> <Step> ### Deploy [#deploy] No additional configuration required. </Step> <Step> ### Test your deployment [#test-your-deployment] ```bash curl "https://your-worker.workers.dev/metadata?url=https://example.com" ``` </Step> </Steps> ## Local Development [#local-development] ```bash cd apps/experiments/website-metadata-extractor npm install npm run dev ``` Test locally: ```bash curl "http://localhost:8787/metadata?url=https://example.com" ``` ## Cloudflare Features Used [#cloudflare-features-used] * **[Workers](https://developers.cloudflare.com/workers/)** - Edge compute runtime * **[Fetch API](https://developers.cloudflare.com/workers/runtime-apis/fetch/)** - HTTP requests for HTML metadata extraction # Website to API (/docs/experiments/website-to-api) Convert any webpage into clean, structured JSON data. Extract the title, all headings with their hierarchy, internal and external links, and images. Perfect for web scraping, content analysis, or building custom search indices. ## API Reference [#api-reference] ### GET /api [#get-api] Transform a webpage into structured JSON by providing its URL. <TypeTable type="{ url: { description: "The URL of the webpage to convert. Must be a valid HTTP or HTTPS URL.", type: "string", required: true, }, }" /> #### Example Request [#example-request] ```bash curl "https://your-worker.workers.dev/api?url=https://www.cloudflare.com" ``` #### Response Structure [#response-structure] **`success`** `boolean` Indicates if the request was successful **`data`** `object` The structured data extracted from the webpage **`data.title`** `string | null` The page title extracted from the `<title>` tag **`data.headings`** `array` Array of all headings (h1-h6) found on the page **`data.headings[].level`** `number` The heading level (1-6 corresponding to h1-h6) **`data.headings[].text`** `string` The text content of the heading (HTML tags stripped) **`data.links`** `string[]` Array of unique absolute URLs extracted from `<a>` tags. Excludes anchor links (#) and javascript: links. All relative URLs are resolved to absolute URLs. **`data.images`** `string[]` Array of unique absolute image URLs extracted from `<img>` tags. All relative URLs are resolved to absolute URLs. #### Example Response [#example-response] ```json { "success": true, "data": { "title": "Cloudflare - The Web Performance & Security Company", "headings": [ { "level": 1, "text": "Welcome to Cloudflare" }, { "level": 2, "text": "Performance" }, { "level": 2, "text": "Security" }, { "level": 3, "text": "DDoS Protection" } ], "links": [ "https://www.cloudflare.com/products/", "https://www.cloudflare.com/plans/", "https://www.cloudflare.com/learning/", "https://developers.cloudflare.com/" ], "images": [ "https://www.cloudflare.com/img/logo-web-badges/cf-logo-on-white-bg.svg", "https://www.cloudflare.com/img/products/workers.png" ] } } ``` ## Error Responses [#error-responses] ### Invalid URL [#invalid-url] ```json { "success": false, "error": "Missing or invalid query parameter: url", "code": "INVALID_URL" } ``` ### Fetch Error [#fetch-error] ```json { "success": false, "error": "HTTP 404", "code": "FETCH_ERROR" } ``` ## Technical Details [#technical-details] * Built with [Hono](https://hono.dev/) framework * Runs on Cloudflare Workers * Regex-based HTML parsing for fast extraction * Automatically resolves relative URLs to absolute URLs * Deduplicates links and images * Returns clean, structured JSON ready for further processing ## Processing Notes [#processing-notes] * All HTML tags within headings are stripped, returning clean text * Anchor links (starting with #) are excluded from the links array * JavaScript URLs (javascript:) are excluded from the links array * Duplicate links and images are automatically removed * Relative URLs are resolved based on the requested page URL ## Use Cases [#use-cases] * **Web Scraping**: Extract structured data from websites without parsing HTML * **Content Analysis**: Analyze page structure and heading hierarchy * **Link Extraction**: Build sitemaps or discover related content * **Search Indexing**: Extract text and structure for custom search engines * **Content Migration**: Extract content when migrating between platforms * **SEO Audits**: Analyze heading structure and internal linking ## Limitations [#limitations] * Static HTML parsing only; JavaScript-rendered content is not available * Fetch timeout and HTML size limits apply * Structure extraction is heuristic; not a full DOM or accessibility tree ## Deployment [#deployment] <Steps> <Step> ### Click the deploy button [#click-the-deploy-button] [![Deploy to Cloudflare Workers](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/shrinathsnayak/cloudflare-experiments/tree/main/apps/experiments/website-to-api) </Step> <Step> ### Deploy [#deploy] No additional configuration required. </Step> <Step> ### Test your deployment [#test-your-deployment] ```bash curl "https://your-worker.workers.dev/api?url=https://example.com" ``` </Step> </Steps> ## Local Development [#local-development] ```bash cd apps/experiments/website-to-api npm install npm run dev ``` Test locally: ```bash curl "http://localhost:8787/api?url=https://example.com" ``` ## Cloudflare Features Used [#cloudflare-features-used] * **[Workers](https://developers.cloudflare.com/workers/)** - Edge compute runtime * **[Fetch API](https://developers.cloudflare.com/workers/runtime-apis/fetch/)** - Remote HTML retrieval and structured parsing # Website to llms.txt (/docs/experiments/website-to-llms-txt) Convert any webpage into the llms.txt format, a structured markdown format optimized for Large Language Model consumption. Extracts title, description, key links, and contact information in a standardized format that LLMs can easily parse. ## API Reference [#api-reference] ### GET /llms.txt [#get-llmstxt] Convert a webpage to llms.txt format by providing its URL. <TypeTable type="{ url: { description: "The URL of the webpage to convert. Must be a valid HTTP or HTTPS URL.", type: "string", required: true, }, }" /> #### Example Request [#example-request] ```bash curl "https://your-worker.workers.dev/llms.txt?url=https://www.cloudflare.com" ``` #### Response Format [#response-format] The endpoint returns plain text in llms.txt format with `Content-Type: text/plain; charset=utf-8`. #### Example Response [#example-response] ```markdown # Cloudflare - The Web Performance & Security Company > Here at Cloudflare, we make the Internet work the way it should. Offering CDN, DNS, DDoS protection and security, find out how we can help your site. ## Key Information - [Products](https://www.cloudflare.com/products/) - [Solutions](https://www.cloudflare.com/solutions/) - [Pricing](https://www.cloudflare.com/plans/) - [Developers](https://developers.cloudflare.com/) - [Learning Center](https://www.cloudflare.com/learning/) - [Community](https://community.cloudflare.com/) - [Support](https://www.cloudflare.com/support/) ## Contact - [Contact Sales](mailto:sales@cloudflare.com) - [Support Team](mailto:support@cloudflare.com) ``` ## Format Specification [#format-specification] The llms.txt format follows this structure: 1. **Title (H1)**: The page title or site name 2. **Description (Blockquote)**: Meta description or og:description 3. **Key Information (H2)**: Up to 100 important links from the page with anchor text 4. **Contact (H2)**: Contact information (mailto links or fallback to website URL) ## Error Responses [#error-responses] ### Invalid URL [#invalid-url] ```json { "success": false, "error": "Missing or invalid query parameter: url", "code": "INVALID_URL" } ``` ### Fetch Error [#fetch-error] ```json { "success": false, "error": "HTTP 404", "code": "FETCH_ERROR" } ``` ## Technical Details [#technical-details] * Built with [Hono](https://hono.dev/) framework * Runs on Cloudflare Workers * Implements llms.txt specification v1.1.1 * Extracts up to 100 key links from the page * Prioritizes metadata over HTML content for descriptions * Returns plain text with UTF-8 encoding ## Extraction Logic [#extraction-logic] ### Title [#title] 1. Extracts from `<title>` tag 2. Falls back to hostname if no title found ### Description [#description] 1. Checks `<meta name="description">` tag 2. Falls back to `<meta property="og:description">` tag 3. Falls back to generic description with the URL ### Key Links [#key-links] * Extracts links from `<a>` tags throughout the page * Includes anchor text with each link * Limits to 100 links maximum * Excludes anchor links (#), javascript:, and mailto: links * Deduplicates by URL * Resolves relative URLs to absolute URLs * Truncates long anchor text to 200 characters ### Contact Information [#contact-information] * Extracts mailto: links if available * Falls back to website URL if no contact links found ## Use Cases [#use-cases] * **LLM Context**: Provide structured website information to language models * **AI Assistants**: Enable AI to understand website structure and navigation * **Documentation Parsing**: Convert documentation sites into LLM-friendly format * **Content Summarization**: Extract key information for AI-powered summaries * **Chatbot Training**: Generate training data from website content * **RAG Systems**: Prepare website data for retrieval-augmented generation ## Limitations [#limitations] * Static HTML extraction; client-rendered content is not executed * Produces a simplified llms.txt for one URL, not a full site crawl * Contact section only finds `mailto:` links present in the HTML ## Deployment [#deployment] <Steps> <Step> ### Click the deploy button [#click-the-deploy-button] [![Deploy to Cloudflare Workers](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/shrinathsnayak/cloudflare-experiments/tree/main/apps/experiments/website-to-llms-txt) </Step> <Step> ### Deploy [#deploy] No additional configuration required. </Step> <Step> ### Test your deployment [#test-your-deployment] ```bash curl "https://your-worker.workers.dev/llms.txt?url=https://example.com" ``` </Step> </Steps> ## Local Development [#local-development] ```bash cd apps/experiments/website-to-llms-txt npm install npm run dev ``` Test locally: ```bash curl "http://localhost:8787/llms.txt?url=https://example.com" ``` ## Cloudflare Features Used [#cloudflare-features-used] * **[Workers](https://developers.cloudflare.com/workers/)** - Edge compute runtime * **[Fetch API](https://developers.cloudflare.com/workers/runtime-apis/fetch/)** - Remote HTML retrieval for llms.txt conversion # WebSocket Echo (/docs/experiments/websocket-echo) Upgrade HTTP requests to WebSocket connections and echo every message back from the edge using the Workers WebSocket API. ## API Reference [#api-reference] ### GET /echo [#get-echo] Accepts a WebSocket upgrade and echoes each message with an `echo: ` prefix. Requires `Upgrade: websocket` on the incoming request. #### Example Request [#example-request] Connect with any WebSocket client: ```bash websocat wss://your-worker.workers.dev/echo ``` Send `hello` and receive `echo: hello`. #### Non-WebSocket Response [#non-websocket-response] When the request is not a WebSocket upgrade, the worker returns HTTP 426: ```json { "error": "Expected WebSocket upgrade request (Upgrade: websocket)", "code": "NOT_WEBSOCKET" } ``` #### Error Codes [#error-codes] * `426` - Request missing WebSocket upgrade (`NOT_WEBSOCKET`) ## Use Cases [#use-cases] * Learn WebSocket upgrades with `WebSocketPair` in Workers * Test real-time clients against a minimal echo server * Prototype bidirectional edge apps before adding Durable Objects * Debug WebSocket connectivity from browsers and CLI tools ## Limitations [#limitations] * Echo server only; no persistence, rooms, or broadcast between clients * No authentication or explicit message size limits in this demo * WebSocket connections are subject to Worker CPU and duration limits ## Deployment [#deployment] <Steps> <Step> ### Click the deploy button [#click-the-deploy-button] [![Deploy to Cloudflare Workers](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/shrinathsnayak/cloudflare-experiments/tree/main/apps/experiments/websocket-echo) </Step> <Step> ### Deploy [#deploy] No additional configuration required. </Step> <Step> ### Test your deployment [#test-your-deployment] Connect with `websocat`, a browser WebSocket client, or your app's WebSocket library to `wss://your-worker.workers.dev/echo`. </Step> </Steps> ## Local Development [#local-development] ```bash cd apps/experiments/websocket-echo npm install npm run dev ``` Connect to `ws://localhost:8787/echo` with a WebSocket client. ## Cloudflare Features Used [#cloudflare-features-used] * **[Workers](https://developers.cloudflare.com/workers/)** - Edge compute runtime * **[WebSockets](https://developers.cloudflare.com/workers/runtime-apis/websockets/)** - `WebSocketPair` for bidirectional messaging # Where Am I? (/docs/experiments/whereami) Retrieve detailed metadata about incoming requests from Cloudflare's edge network, including geographic location, data center, ASN, and timezone. ## API Reference [#api-reference] ### GET /whereami [#get-whereami] Returns the Cloudflare `request.cf` object containing metadata about the incoming request. #### Example Request [#example-request] ```bash curl "https://your-worker.workers.dev/whereami" ``` #### Success Response [#success-response] The response includes whatever Cloudflare injects into `request.cf` for the incoming request. Common fields include: **`country`** `string` Two-letter ISO 3166-1 country code (e.g., "US", "GB") **`city`** `string` City name (e.g., "San Francisco") **`region`** `string` Region or state name (e.g., "California") **`timezone`** `string` IANA timezone identifier (e.g., "America/Los\_Angeles") **`colo`** `string` Cloudflare data center IATA code (e.g., "SFO", "LHR") **`asn`** `number` Autonomous System Number of the client's ISP ```json { "country": "US", "city": "San Francisco", "region": "California", "timezone": "America/Los_Angeles", "colo": "SFO", "asn": 13335 } ``` <Callout> The exact fields returned depend on Cloudflare's detection capabilities. During local development, the response may be empty or contain minimal data. Full metadata is available when deployed. </Callout> ## Additional Request Metadata Fields [#additional-request-metadata-fields] Depending on the request, the `cf` object may also include: * `latitude` / `longitude` - Geographic coordinates * `postalCode` - Postal/ZIP code * `metroCode` - Metro area code * `continent` - Two-letter continent code * `regionCode` - ISO 3166-2 region code * `tlsVersion` - TLS version used (e.g., "TLSv1.3") * `tlsCipher` - TLS cipher suite * `httpProtocol` - HTTP protocol version (e.g., "HTTP/2") See the [Cloudflare request.cf documentation](https://developers.cloudflare.com/workers/runtime-apis/request/#incomingrequestcfproperties) for a complete list. ## Implementation Details [#implementation-details] * Completely stateless - no external requests * No database or KV storage required * Returns raw `request.cf` object from Cloudflare * Works immediately after deployment ## Use Cases [#use-cases] * Determine visitor geographic location without external APIs * Display localized content based on country/region * Show the nearest edge data center serving the request * Analyze traffic patterns by ASN or location * Build geographic routing logic ## Limitations [#limitations] * Returns Cloudflare `request.cf` metadata only; not a standalone geolocation API * Local development returns minimal or empty `cf` data * Available fields vary by request path and Cloudflare detection capabilities ## Deployment [#deployment] <Steps> <Step> ### Click the deploy button [#click-the-deploy-button] [![Deploy to Cloudflare Workers](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/shrinathsnayak/cloudflare-experiments/tree/main/apps/experiments/whereami) </Step> <Step> ### Deploy [#deploy] Follow the deployment wizard to deploy the Worker to your Cloudflare account. No additional configuration required. </Step> <Step> ### Test your deployment [#test-your-deployment] ```bash curl "https://your-worker.workers.dev/whereami" ``` </Step> </Steps> ## Local Development [#local-development] ```bash cd apps/experiments/whereami npm install npm run dev ``` Test locally: ```bash curl "http://localhost:8787/whereami" ``` <Callout type="warning"> Local development will return an empty object or minimal data since `request.cf` is only populated by Cloudflare's edge network. </Callout> ## Cloudflare Features Used [#cloudflare-features-used] * **[Workers](https://developers.cloudflare.com/workers/)** - Edge compute runtime * **[request.cf](https://developers.cloudflare.com/workers/runtime-apis/request/#incomingrequestcfproperties)** - Incoming request metadata including geolocation # Workflows Pipeline Demo (/docs/experiments/workflows-pipeline-demo) A durable three-step pipeline built with **Cloudflare Workflows**: fetch a URL, pause with `step.sleep()`, summarize the page with Workers AI, and store the JSON result in R2. Poll status by workflow instance ID. ## Features [#features] * **POST /run** - Start a workflow instance from a URL * **GET /status/:instanceId** - Poll status and final output * **Three durable steps** - fetch, summarize, store (each wrapped in `step.do()`) * **Pause demo** - `step.sleep("3 seconds")` between fetch and summarize ## API Reference [#api-reference] ### POST /run [#post-run] Start a new workflow instance. <TypeTable type="{ url: { description: "Target page URL (http or https only).", type: "string", required: true, }, }" /> #### Example Request [#example-request] ```bash curl -X POST "https://your-worker.workers.dev/run" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com"}' ``` #### Success Response (202) [#success-response-202] ```json { "instanceId": "abc-123", "status": "running" } ``` #### Error Codes [#error-codes] * `400` - Invalid JSON (`INVALID_BODY`) or URL (`INVALID_URL`) * `502` - Workflow start failed (`WORKFLOW_ERROR`) ### GET /status/:instanceId [#get-statusinstanceid] Poll workflow status and result. #### Example Request [#example-request-1] ```bash curl "https://your-worker.workers.dev/status/abc-123" ``` #### Success Response [#success-response] ```json { "instanceId": "abc-123", "status": "complete", "output": { "url": "https://example.com/", "summary": "Example Domain is a placeholder site for documentation examples.", "r2Key": "summaries/abc-123.json" }, "error": null } ``` #### Error Codes [#error-codes-1] * `400` - Missing instance id (`INVALID_INSTANCE_ID`) * `404` - Instance not found (`NOT_FOUND`) ## Use Cases [#use-cases] * Learn `WorkflowEntrypoint`, `step.do()`, and `step.sleep()` patterns * Build durable multi-step pipelines that survive retries and pauses * Combine Workflows with Workers AI and R2 in one reference worker ## Limitations [#limitations] * Demo HTML extraction is basic (title + stripped tags); not suitable for complex SPAs * Requires R2 bucket, Workers AI, and Workflows enabled on your account * Workflow sleep adds latency by design for the demo ## Deployment [#deployment] <Steps> <Step> ### Click the deploy button [#click-the-deploy-button] [![Deploy to Cloudflare Workers](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/shrinathsnayak/cloudflare-experiments/tree/main/apps/experiments/workflows-pipeline-demo) </Step> <Step> ### Configure bindings [#configure-bindings] Create R2 bucket `workflows-pipeline-summaries` and enable Workers AI. The workflow binding is declared in `wrangler.json`. </Step> <Step> ### Test your deployment [#test-your-deployment] ```bash curl -X POST "https://your-worker.workers.dev/run" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com"}' ``` </Step> </Steps> ## Local Development [#local-development] ```bash cd apps/experiments/workflows-pipeline-demo npm install npm run dev ``` ## Configuration [#configuration] | Binding | Purpose | | ----------- | --------------------------------------------- | | `PIPELINE` | Cloudflare Workflow (`SummaryPipeline` class) | | `AI` | Workers AI summarization | | `SUMMARIES` | R2 bucket for stored JSON summaries | ## Cloudflare Features Used [#cloudflare-features-used] * **[Workers](https://developers.cloudflare.com/workers/)** - Edge compute runtime * **[Workflows](https://developers.cloudflare.com/workflows/)** - Durable multi-step execution * **[Workers AI](https://developers.cloudflare.com/workers-ai/)** - LLM summarization * **[R2](https://developers.cloudflare.com/r2/)** - Object storage for pipeline output # Architecture & Design Patterns (/docs/reference/architecture) All experiments follow consistent architectural patterns and design principles. This guide explains the shared architecture, tech stack, and best practices used throughout the repository. ## Standard Experiment Layout [#standard-experiment-layout] <Files> <Folder name="apps/experiments/<name>"> <Folder name="src"> <File name="index.ts" /> <Folder name="routes"> <File name="check.ts" /> </Folder> <Folder name="lib"> <File name="url.ts" /> <File name="fetch.ts" /> </Folder> <Folder name="utils"> <File name="response.ts" /> </Folder> <Folder name="types"> <File name="env.d.ts" /> </Folder> <Folder name="constants"> <File name="defaults.ts" /> </Folder> </Folder> <Folder name="test"> <File name="smoke.test.ts" /> </Folder> <File name="wrangler.json" /> <File name="package.json" /> <File name="README.md" /> </Folder> </Files> ## Tech Stack [#tech-stack] Every experiment uses a consistent, modern tech stack optimized for edge computing: ### Core Technologies [#core-technologies] <Cards> <Card title="TypeScript"> **Strictly typed** codebase with full type safety. All experiments use TypeScript with: * Strict mode enabled * No `any` types * Full type coverage * `tsconfig.json` per experiment Environment bindings are typed in `src/types/env.d.ts`. </Card> <Card title="Hono"> **Fast, lightweight** web framework for Cloudflare Workers. Benefits: - Ultra-small bundle size (\~12KB) - Type-safe routing - Middleware support - Web standard APIs - Built for edge runtime Used for routing in all experiments. </Card> <Card title="Cloudflare Workers"> **Serverless runtime** at the edge. Features: - V8 isolate-based execution - Standard Web APIs - Sub-millisecond cold starts - Global deployment - Zero configuration scaling </Card> <Card title="Wrangler"> **CLI tool** for Workers development and deployment. Used for: * Local development (`wrangler dev`) * Deployment (`wrangler deploy`) * Resource management (D1, KV, R2) * Secret management </Card> </Cards> ## Project Structure [#project-structure] Every experiment follows an identical directory structure for consistency: ``` apps/experiments/<name>/ ├── src/ │ ├── index.ts # Entry point - Hono app, route mounting, error handler │ ├── routes/ # Route handlers (one file per route or logical group) │ ├── lib/ # Business logic (fetch, parsing, validation) │ ├── utils/ # Shared utilities (response helpers, formatters) │ ├── constants/ # Configuration and constants │ └── types/ # TypeScript types and interfaces │ └── env.d.ts # Worker environment bindings ├── migrations/ # Database migrations (D1 experiments only) ├── package.json # Dependencies and scripts ├── wrangler.json # Worker configuration and bindings ├── tsconfig.json # TypeScript configuration └── README.md # Experiment documentation ``` ### Key Principles [#key-principles] 1. **Single Responsibility**: One experiment demonstrates one Cloudflare feature 2. **No Shared Code**: Each experiment is completely independent 3. **Consistent Structure**: Same directory layout across all experiments 4. **Type Safety**: Full TypeScript coverage with strict mode ## Common Patterns [#common-patterns] Experiments implement several recurring architectural patterns: ### 1. Fetch + Parse Pattern [#1-fetch--parse-pattern] Fetch external content and extract structured data. **Used in**: Dependency Analyzer, Website Metadata Extractor, Website DevTools Inspector ```typescript // src/routes/analyze.ts import { Hono } from "hono"; import { validateUrl } from "../lib/url"; import { fetchHtml } from "../lib/fetch"; import { parseDependencies } from "../lib/parse"; import { jsonError, jsonSuccess } from "../utils/response"; const app = new Hono<{ Bindings: Env }>(); app.get("/analyze", async (c) => { // 1. Validate input const url = validateUrl(c.req.query("url")); if (!url) return jsonError(c, "Missing or invalid url", "INVALID_URL"); try { // 2. Fetch content const html = await fetchHtml(url); // 3. Parse and extract const data = parseDependencies(html, url); // 4. Return structured response return jsonSuccess(c, data); } catch (e) { const message = e instanceof Error ? e.message : "Failed to fetch"; return jsonError(c, message, "FETCH_ERROR", 502); } }); ``` **Pattern Flow:** 1. Validate user input (URL) 2. Fetch external content 3. Parse/extract data with regex or HTMLRewriter 4. Return JSON response 5. Handle errors consistently ### 2. AI Integration Pattern [#2-ai-integration-pattern] Fetch content, extract text, and process with Workers AI. **Used in**: AI Website Summary, GitHub Repo Explainer ```typescript // src/routes/summary.ts import { Hono } from "hono"; import { validateUrl } from "../lib/url"; import { fetchHtml } from "../lib/fetch"; import { getTitle, getBodyText } from "../lib/html"; import { summarizeWithAi } from "../lib/ai"; import { jsonSuccess, jsonError } from "../utils/response"; const app = new Hono<{ Bindings: Env }>(); app.get("/summary", async (c) => { const url = validateUrl(c.req.query("url")); if (!url) return jsonError(c, "Missing or invalid url", "INVALID_URL"); try { // 1. Fetch HTML const html = await fetchHtml(url); // 2. Extract text content const title = getTitle(html); const bodyText = getBodyText(html, 6000); if (!bodyText.trim()) { return jsonSuccess(c, { title, summary: "No content." }); } // 3. Summarize with AI const summary = await summarizeWithAi(c.env, bodyText, title); // 4. Return result return jsonSuccess(c, { title, summary }); } catch (e) { const message = e instanceof Error ? e.message : "Failed"; return jsonError(c, message, "FETCH_OR_AI_ERROR", 502); } }); ``` **AI Helper Example:** ```typescript // src/lib/ai.ts import type { Env } from "../types/env"; export async function summarizeWithAi( env: Env, text: string, title: string | null ): Promise<string> { const prompt = `Summarize the following webpage content.\n\nTitle: ${title || "N/A"}\n\nContent:\n${text}`; const response = await env.AI.run("@cf/meta/llama-3-8b-instruct", { messages: [ { role: "system", content: "You are a helpful assistant." }, { role: "user", content: prompt }, ], }); return response.response || "Unable to generate summary."; } ``` **Pattern Flow:** 1. Fetch external content 2. Extract relevant text (limit to model context) 3. Send to Workers AI with structured prompt 4. Return AI response ### 3. Browser Automation Pattern [#3-browser-automation-pattern] Use headless browser to interact with pages. **Used in**: Screenshot API ```typescript // src/routes/screenshot.ts import { Hono } from "hono"; import puppeteer from "@cloudflare/puppeteer"; import { validateUrl } from "../lib/url"; import { jsonError } from "../utils/response"; import { DEFAULT_VIEWPORT, NAVIGATION_TIMEOUT_MS } from "../constants/defaults"; const app = new Hono<{ Bindings: Env }>(); app.get("/screenshot", async (c) => { const url = validateUrl(c.req.query("url")); if (!url) return jsonError(c, "Missing or invalid url", "INVALID_URL"); let browser = null; try { // 1. Launch browser browser = await puppeteer.launch(c.env.BROWSER); // 2. Create page and configure const page = await browser.newPage(); await page.setViewport(DEFAULT_VIEWPORT); // 3. Navigate and wait await page.goto(url, { waitUntil: "networkidle0", timeout: NAVIGATION_TIMEOUT_MS, }); // 4. Capture screenshot const png = await page.screenshot({ type: "png" }); // 5. Clean up await browser.close(); browser = null; // 6. Return image return new Response(png, { headers: { "Content-Type": "image/png", "Cache-Control": "public, max-age=60", }, }); } catch (e) { if (browser) try { await browser.close(); } catch { /* ignore */ } const message = e instanceof Error ? e.message : "Screenshot failed"; return jsonError(c, message, "SCREENSHOT_ERROR", 502); } }); ``` **Pattern Flow:** 1. Launch browser session 2. Configure viewport and navigation options 3. Navigate to URL and wait for load 4. Perform action (screenshot, scraping, etc.) 5. Always close browser (cleanup) 6. Return binary or JSON response ### 4. Storage Pattern (D1 + KV) [#4-storage-pattern-d1--kv] Combine D1 (primary) with KV (cache) for optimal performance. **Used in**: Link Shortener ```typescript // src/routes/redirect.ts import { Hono } from "hono"; import type { Env } from "../types/env"; import { jsonError } from "../utils/response"; const app = new Hono<{ Bindings: Env }>(); app.get("/:code", async (c) => { const code = c.req.param("code"); // 1. Try KV cache first (fast read) let url = await c.env.LINKS_CACHE.get(code); if (!url) { // 2. Cache miss - read from D1 (source of truth) const row = await c.env.DB.prepare("SELECT url FROM links WHERE code = ?") .bind(code) .first<{ url: string }>(); if (!row) { return jsonError(c, "Link not found", "NOT_FOUND", 404); } url = row.url; // 3. Populate cache for future requests await c.env.LINKS_CACHE.put(code, url); } // 4. Redirect return c.redirect(url, 302); }); ``` **Storage Strategy:** * **D1**: Source of truth, handles writes and cache misses * **KV**: Read-through cache, optimized for redirects * **Pattern**: Read from KV → Miss → Read D1 → Cache in KV → Return ### 5. Object Storage Pattern [#5-object-storage-pattern] Public and private R2 buckets with direct access. **Used in**: R2 Storage ```typescript // src/routes/object.ts import { Hono } from "hono"; import type { Env } from "../types/env"; const app = new Hono<{ Bindings: Env }>(); app.put("/object", async (c) => { const key = c.req.query("key"); const isPublic = c.req.query("public") === "true"; if (!key) return c.json({ error: "Missing key" }, 400); // Choose bucket based on public flag const bucket = isPublic ? c.env.PUBLIC_BUCKET : c.env.BUCKET; // Upload object await bucket.put(key, c.req.raw.body, { httpMetadata: { contentType: c.req.header("Content-Type") || "application/octet-stream", }, }); // Generate public URL if applicable const response: any = { key, uploaded: true }; if (isPublic && c.env.PUBLIC_BUCKET_URL) { response.url = `${c.env.PUBLIC_BUCKET_URL}/${key}`; } return c.json(response); }); ``` **Storage Strategy:** * **Private bucket**: Objects only accessible via Worker * **Public bucket**: Direct URLs for CDN delivery * **Pattern**: Choose bucket → Upload → Return URL (public) or key (private) ## Error Handling [#error-handling] Consistent error handling across all experiments: ### Response Utilities [#response-utilities] ```typescript // src/utils/response.ts import type { Context } from "hono"; export function jsonSuccess<T>(c: Context, data: T) { return c.json(data); } export function jsonError(c: Context, message: string, code: string, status: number = 400) { return c.json({ error: message, code }, status); } ``` ### Global Error Handler [#global-error-handler] Every experiment registers a global error handler: ```typescript // src/index.ts import { Hono } from "hono"; import type { Env } from "./types/env"; import routes from "./routes/..."; const app = new Hono<{ Bindings: Env }>(); app.route("/", routes); app.get("/", (c) => { return c.json({ name: "experiment-name", description: "What this experiment does", usage: "GET /endpoint?param=value", }); }); // Global error handler for uncaught errors app.onError((err, c) => { return c.json({ error: err.message, code: "INTERNAL_ERROR" }, 500); }); export default { fetch: app.fetch }; ``` ### Error Codes [#error-codes] Standardized error codes used across experiments: | Code | Status | Description | | ------------------ | ------ | ----------------------------------- | | `INVALID_URL` | 400 | Missing or malformed URL parameter | | `INVALID_INPUT` | 400 | Invalid request body or parameters | | `NOT_FOUND` | 404 | Resource not found (links, objects) | | `FETCH_ERROR` | 502 | Failed to fetch external resource | | `PARSE_ERROR` | 502 | Failed to parse HTML or response | | `AI_ERROR` | 502 | Workers AI inference failed | | `SCREENSHOT_ERROR` | 502 | Browser Rendering failed | | `DATABASE_ERROR` | 500 | D1 query failed | | `STORAGE_ERROR` | 500 | KV or R2 operation failed | | `INTERNAL_ERROR` | 500 | Uncaught/unexpected error | ## Validation [#validation] Input validation is critical for security and reliability: ### URL Validation [#url-validation] ```typescript // src/lib/url.ts export function validateUrl(input: string | undefined): string | null { if (!input) return null; try { const url = new URL(input); // Only allow HTTP and HTTPS if (url.protocol !== "http:" && url.protocol !== "https:") { return null; } return url.toString(); } catch { return null; } } export function resolveUrl(base: string, href: string): string { try { return new URL(href, base).toString(); } catch { return href; } } ``` ### Request Body Validation [#request-body-validation] ```typescript // src/routes/shorten.ts app.post("/shorten", async (c) => { let body: any; try { body = await c.req.json(); } catch { return jsonError(c, "Invalid JSON", "INVALID_INPUT"); } const url = validateUrl(body?.url); if (!url) { return jsonError(c, "Missing or invalid url in body", "INVALID_URL"); } // Proceed with validated input }); ``` ## HTML Parsing [#html-parsing] Experiments use regex-based parsing for lightweight HTML extraction: ```typescript // src/lib/parse.ts function extractScripts(html: string): string[] { const urls: string[] = []; const re = /<script[^>]+src=["']([^"']+)["']/gi; let m: RegExpExecArray | null; while ((m = re.exec(html)) !== null) { urls.push(m[1].trim()); } return urls; } function getTitle(html: string): string | null { const m = html.match(/<title[^>]*>([\s\S]*?)<\/title>/i); return m ? m[1].replace(/<[^>]+>/g, "").trim() || null : null; } function getMeta(html: string, name: string): string | null { const re = new RegExp(`<meta[^>]+name=["']${name}["'][^>]+content=["']([^"']*)["']`, "i"); const m = html.match(re); return m ? m[1].trim() || null : null; } ``` **Why Regex Instead of DOM Parser?** * Lower memory overhead * Faster for simple extraction * No need for full DOM representation * Works well at the edge with streaming ## Configuration [#configuration] Configuration is managed via `wrangler.json` and environment variables: ### wrangler.json Structure [#wranglerjson-structure] ```json { "name": "experiment-name", "main": "src/index.ts", "compatibility_date": "2024-01-01", "node_compat": true, "vars": { "PUBLIC_URL": "https://example.com" }, "ai": { "binding": "AI" }, "browser": { "binding": "BROWSER" }, "d1_databases": [ { "binding": "DB", "database_name": "my-database", "database_id": "00000000-0000-0000-0000-000000000000" } ], "kv_namespaces": [ { "binding": "CACHE", "id": "00000000000000000000000000000000" } ], "r2_buckets": [ { "binding": "BUCKET", "bucket_name": "my-bucket" } ] } ``` ### Environment Bindings [#environment-bindings] Type bindings in `src/types/env.d.ts`: ```typescript export interface Env { // AI binding AI?: Ai; // Browser binding BROWSER?: Fetcher; // D1 database DB?: D1Database; // KV namespace CACHE?: KVNamespace; // R2 bucket BUCKET?: R2Bucket; PUBLIC_BUCKET?: R2Bucket; // Environment variables PUBLIC_BUCKET_URL?: string; } ``` ## Performance Optimization [#performance-optimization] Experiments follow edge-optimized performance patterns: ### 1. Minimize Latency [#1-minimize-latency] * Use edge-native APIs (fetch, KV) * Stream responses when possible * Avoid buffering large payloads ### 2. Optimize Cold Starts [#2-optimize-cold-starts] * Keep bundle size small (Hono \~12KB) * Minimal dependencies * Tree-shaking via esbuild ### 3. Cache Effectively [#3-cache-effectively] * Use KV for read-heavy data * Set appropriate Cache-Control headers * Leverage Cloudflare's CDN ### 4. Handle Timeouts [#4-handle-timeouts] * Set reasonable fetch timeouts * Browser navigation timeouts * Graceful degradation ## Security Best Practices [#security-best-practices] <Cards> <Card title="Input Validation"> Always validate user input: * URL format and protocol * JSON structure * Parameter types and ranges </Card> <Card title="Error Messages"> Never expose internal details: - Generic error messages for users - Specific errors logged internally - No stack traces in responses </Card> <Card title="Rate Limiting"> Protect against abuse: - Cloudflare Rate Limiting rules - Per-IP limits - Per-endpoint limits </Card> <Card title="Secret Management"> Store secrets securely: * Use Wrangler secrets (encrypted) * Never commit secrets to git * Rotate keys regularly </Card> </Cards> ## Testing Strategy [#testing-strategy] Test experiments locally before deployment: ```bash # Type checking npm run build # or: tsc --noEmit # Local development npm run dev # Remote testing (for AI, Browser, R2) npm run dev -- --remote # Manual endpoint testing curl http://localhost:8787/endpoint?param=value ``` ## Next Steps [#next-steps] <Cards> <Card title="Cloudflare Features" href="/reference/cloudflare-features"> Learn about platform features used in experiments </Card> <Card title="Deployment Guide" href="/reference/deployment"> Deploy experiments to production </Card> <Card title="Contributing" href="https://github.com/shrinathsnayak/cloudflare-experiments/blob/main/CONTRIBUTING.md"> Add your own experiment to the repository </Card> </Cards> # Cloudflare Features (/docs/reference/cloudflare-features) The experiments in this repository demonstrate various Cloudflare platform features. Each feature enables developers to build powerful applications that run at the edge, close to users worldwide. ## Core Features [#core-features] <Cards> <Card title="Cloudflare Workers"> **Edge compute platform** that runs JavaScript/TypeScript on Cloudflare's global network. All experiments run on Workers, providing: * Global deployment in 300+ cities * Sub-millisecond cold starts * Zero-config scaling * Request/Response manipulation * Standard Web APIs (fetch, URL, etc.) Used in: All experiments </Card> <Card title="Workers AI"> **Serverless AI inference** at the edge with built-in models. Run language models directly from Workers: - Text generation and summarization - No GPU infrastructure required - Pay-per-inference pricing - Multiple model options Used in: - [AI Website Summary](/experiments/ai-website-summary) * [GitHub Repo Explainer](/experiments/github-repo-explainer) - [Cloud AI Proxy](/experiments/cloud-ai-proxy) </Card> <Card title="Browser Rendering"> **Headless browser** automation powered by Puppeteer on the edge. Capture screenshots, generate PDFs, and interact with web pages: * Full Chrome browser in Workers * Screenshot and PDF generation * Page metrics and DOM evaluation * No browser infrastructure to manage Used in: * [Screenshot API](/experiments/screenshot-api) * [PDF API](/experiments/pdf-api) * [Page Metrics](/experiments/page-metrics) * [Rendered Text](/experiments/rendered-text) * [Browser Links](/experiments/browser-links) </Card> <Card title="HTMLRewriter"> **Streaming HTML parser** for transforming HTML on the fly. Parse and extract data from HTML without buffering: * Streaming HTML transformation * CSS selector-based parsing * Low memory overhead * Regex-based extraction patterns Used in: * [Dependency Analyzer](/experiments/dependency-analyzer) * [Website Metadata Extractor](/experiments/website-metadata-extractor) * [Website DevTools Inspector](/experiments/website-devtools-inspector) * [Website to API](/experiments/website-to-api) </Card> </Cards> ## Storage Features [#storage-features] <Cards> <Card title="D1 (SQL Database)"> **SQLite database** at the edge with global replication. Serverless SQL for relational data: * SQLite syntax and compatibility * Automatic backups * Read replication * Schema migrations * Low-latency queries from edge Used in: * [Link Shortener](/experiments/link-shortener) (primary storage) </Card> <Card title="Workers KV"> **Global key-value store** optimized for high-read workloads. Distributed KV storage: * Eventually consistent reads * Global edge caching * TTL support * Ideal for read-heavy caching Used in: * [Link Shortener](/experiments/link-shortener) (read-through cache) * [Cron Heartbeat](/experiments/cron-heartbeat) (scheduled run metadata) * [Task Queue](/experiments/task-queue) (enqueue/process stats) * [KV Notes](/experiments/kv-notes) (primary storage) </Card> <Card title="R2 Storage"> **Object storage** compatible with S3 APIs, with zero egress fees. Store files and assets: * S3-compatible API * No egress charges * Public and private buckets * Custom metadata support * Ideal for images, videos, files Used in: * [R2 Storage](/experiments/r2-storage) </Card> </Cards> ## Stateful & Async Features [#stateful--async-features] <Cards> <Card title="Durable Objects"> **Strongly consistent state** at the edge. Single-threaded objects with persistent storage for coordination, counters, sessions, and real-time apps. Used in: * [Durable Counter](/experiments/durable-counter) </Card> <Card title="Cron Triggers"> **Scheduled Workers** that run on a cron schedule without external schedulers. Used in: * [Cron Heartbeat](/experiments/cron-heartbeat) </Card> <Card title="Queues"> **Asynchronous message processing** between Workers. Decouple HTTP requests from background work with producer/consumer handlers. Used in: * [Task Queue](/experiments/task-queue) </Card> </Cards> ## Network Features [#network-features] <Cards> <Card title="Edge Networking"> **Global network** with advanced request handling. Access network-level information: * Geolocation data (country, city, region) * Data center (colo) information * Request metadata * TLS/SSL information Used in: * [Where Am I](/experiments/whereami) * [Is It Down](/experiments/is-it-down) * [Response Headers](/experiments/response-headers) </Card> <Card title="Fetch API"> **Standards-based HTTP client** with edge optimizations. Make HTTP requests from the edge: - Standard Web Fetch API - Automatic retries and failover - Connection pooling - Header manipulation Used in: Most experiments for fetching external content </Card> <Card title="Cache API"> **Edge response cache** for storing fetch responses in the same colo. Cache origin responses with `caches.default`: * Read-through caching patterns * Per-colo cache storage * TTL via response headers Used in: * [Edge Cache](/experiments/edge-cache) </Card> <Card title="Web Crypto API"> **Cryptography at the edge** via `crypto.subtle`. Hash and sign data without external libraries: * SHA digests * HMAC and signing (extensible pattern) * Standards-based Web Crypto Used in: * [Crypto Hash](/experiments/crypto-hash) </Card> <Card title="WebSockets"> **Bidirectional messaging** from Workers using `WebSocketPair`. Upgrade HTTP to WebSocket and push messages from the edge: * Real-time echo and chat patterns * Works with browser and CLI clients * Foundation for Durable Objects WebSockets Used in: * [WebSocket Echo](/experiments/websocket-echo) </Card> </Cards> ## Feature Comparison [#feature-comparison] | Feature | Type | Use Case | Cold Start | Pricing Model | | ----------------- | ------- | ------------------------- | ---------- | --------------------- | | Workers | Compute | Edge functions | \<1ms | Per request | | Workers AI | AI/ML | Inference | \~50ms | Per inference | | Browser Rendering | Compute | Screenshots, scraping | \~2s | Per session | | D1 | Storage | Relational data | \~5ms | Reads + writes | | KV | Storage | Caching | \<1ms | Reads + writes | | R2 | Storage | Object storage | \~10ms | Storage + operations | | Durable Objects | Compute | Strongly consistent state | \~5ms | Requests + storage | | Queues | Async | Background processing | \~10ms | Messages + operations | | Cron Triggers | Compute | Scheduled tasks | \~5ms | Per invocation | | Cache API | Storage | Edge HTTP cache | \<1ms | Per operation | | Web Crypto | Crypto | Hashing, signing | \<1ms | Per request | | WebSockets | Network | Real-time messaging | \~5ms | Per connection | ## Architecture Patterns [#architecture-patterns] Experiments demonstrate several common patterns: ### Fetch + Parse Pattern [#fetch--parse-pattern] Fetch external HTML and parse with regex or HTMLRewriter: * Dependency Analyzer * Website Metadata Extractor * Website DevTools Inspector ### Browser Rendering Pattern [#browser-rendering-pattern] Launch Puppeteer via the `BROWSER` binding, navigate, then screenshot, PDF, metrics, or DOM evaluation: * Screenshot API * PDF API * Page Metrics * Rendered Text * Browser Links ### AI Integration Pattern [#ai-integration-pattern] Fetch content, extract text, and summarize with Workers AI: * AI Website Summary * GitHub Repo Explainer ### Storage Pattern [#storage-pattern] Combine D1 (primary) with KV (cache) for optimal performance: * Link Shortener (D1 + KV) ### KV Storage Pattern [#kv-storage-pattern] Simple key-value CRUD with a dedicated KV namespace: * KV Notes ### Object Storage Pattern [#object-storage-pattern] Public and private R2 buckets with direct URLs: * R2 Storage ### Durable Objects Pattern [#durable-objects-pattern] Single named object for globally consistent state: * Durable Counter ### Scheduled Tasks Pattern [#scheduled-tasks-pattern] Cron handler writes metadata to KV; HTTP route reads status: * Cron Heartbeat ### Queue Producer/Consumer Pattern [#queue-producerconsumer-pattern] HTTP enqueues messages; `queue()` handler processes asynchronously: * Task Queue ### Edge Cache Pattern [#edge-cache-pattern] Fetch through `caches.default` and report cache hit/miss: * Edge Cache ### Web Crypto Pattern [#web-crypto-pattern] Hash input with `crypto.subtle.digest` at the edge: * Crypto Hash ### WebSocket Pattern [#websocket-pattern] Upgrade HTTP to WebSocket with `WebSocketPair`: * WebSocket Echo ## Learn More [#learn-more] <Card title="Cloudflare Developer Documentation" href="https://developers.cloudflare.com"> Official documentation for all Cloudflare developer products </Card> # Deployment Guide (/docs/reference/deployment) Every experiment in this repository can be deployed independently to Cloudflare Workers. This guide covers all deployment methods and configuration steps. ## Quick Deploy (Click-to-Deploy) [#quick-deploy-click-to-deploy] The fastest way to deploy any experiment is using Cloudflare's one-click deployment: <Steps> <Step> ### Choose an experiment [#choose-an-experiment] Navigate to any experiment page and click the "Deploy to Cloudflare Workers" button. Each experiment README includes a deploy button linking to its source code. </Step> <Step> ### Authenticate with Cloudflare [#authenticate-with-cloudflare] Sign in to your Cloudflare account or create one if you don't have an account. The deployment system will request permissions to deploy Workers on your behalf. </Step> <Step> ### Configure resources [#configure-resources] Some experiments require additional resources: * **D1 databases**: Created automatically during deployment * **KV namespaces**: Created automatically * **R2 buckets**: Created automatically * **Browser Rendering**: Enabled automatically (requires plan upgrade) * **Workers AI**: Enabled automatically (requires plan upgrade) The deployment wizard guides you through any required setup. </Step> <Step> ### Deploy [#deploy] Click the final deploy button. Your Worker will be deployed to a `*.workers.dev` subdomain. The deployment typically completes in 10-30 seconds. </Step> </Steps> ### Deployment URL Format [#deployment-url-format] Click-to-deploy URLs follow this pattern: ``` https://deploy.workers.cloudflare.com/?url=https://github.com/{owner}/{repo}/tree/{branch}/apps/experiments/{experiment-name} ``` **Example:** ``` https://deploy.workers.cloudflare.com/?url=https://github.com/shrinathsnayak/cloudflare-experiments/tree/main/apps/experiments/ai-website-summary ``` <Callout> To deploy from your own fork, replace `shrinathsnayak` with your GitHub username in the deployment URL. </Callout> ## Manual Deployment [#manual-deployment] For more control or local development, deploy manually using Wrangler CLI. <Tabs items="['Prerequisites', 'Basic Experiments', 'Workers AI', 'Browser Rendering', 'D1 + KV', 'R2 Storage']"> <Tab value="Prerequisites"> Install the required tools: ```bash # Install Node.js 18+ (via nvm recommended) nvm install 18 nvm use 18 # Install Wrangler CLI globally npm install -g wrangler # Authenticate with Cloudflare wrangler login ``` Clone the repository: ```bash git clone https://github.com/shrinathsnayak/cloudflare-experiments.git cd cloudflare-experiments ``` </Tab> <Tab value="Basic Experiments"> Deploy experiments with no external dependencies (most experiments): ```bash # Navigate to experiment directory cd apps/experiments/is-it-down # Install dependencies npm install # Test locally npm run dev # Visit http://localhost:8787 # Deploy to production npm run deploy # or: wrangler deploy ``` Your Worker will be deployed to `https://{experiment-name}.{your-subdomain}.workers.dev`. <Callout> Basic experiments include: Is It Down, Where Am I, Website Metadata Extractor, Dependency Analyzer, Website to API, URL DNS Lookup, Website DevTools Inspector, AI Bot Visibility, Website to llms.txt </Callout> </Tab> <Tab value="Workers AI"> Experiments using Workers AI require additional setup: ```bash cd apps/experiments/ai-website-summary npm install # Workers AI is automatically bound in wrangler.json # No additional configuration needed # Test with remote AI (local dev doesn't support AI) npm run dev -- --remote # Deploy npm run deploy ``` <Callout type="warning"> Workers AI requires the Workers Paid plan ($5/month). The first 10,000 AI requests per day are included. </Callout> Experiments using Workers AI: * [AI Website Summary](/experiments/ai-website-summary) * [GitHub Repo Explainer](/experiments/github-repo-explainer) * [Cloud AI Proxy](/experiments/cloud-ai-proxy) </Tab> <Tab value="Browser Rendering"> Deploy experiments that capture screenshots: ```bash cd apps/experiments/screenshot-api npm install # Browser Rendering requires remote mode for local testing npm run dev -- --remote # Deploy npm run deploy ``` <Callout type="warning"> Browser Rendering requires the Workers Paid plan. Pricing is per browser session. </Callout> The Browser binding is automatically configured in `wrangler.json`: ```json { "browser": { "binding": "BROWSER" } } ``` </Tab> <Tab value="D1 + KV"> Deploy experiments with database storage: ```bash cd apps/experiments/link-shortener npm install # 1. Create D1 database wrangler d1 create link-shortener-db # Copy the returned database_id # 2. Update wrangler.json with the database_id # Replace 00000000-0000-0000-0000-000000000000 with your database_id # 3. Create KV namespace wrangler kv namespace create LINKS_CACHE # Copy the returned id # 4. Update wrangler.json with the KV namespace id # 5. Run migrations locally npm run db:migrate:local # 6. Test locally npm run dev # 7. Run migrations on production npm run db:migrate # 8. Deploy npm run deploy ``` **wrangler.json structure:** ```json { "d1_databases": [ { "binding": "DB", "database_name": "link-shortener-db", "database_id": "your-database-id-here" } ], "kv_namespaces": [ { "binding": "LINKS_CACHE", "id": "your-kv-namespace-id-here" } ] } ``` <Callout> D1 and KV are included in the Workers Free plan with usage limits. Paid plan removes limits. </Callout> </Tab> <Tab value="R2 Storage"> Deploy experiments with object storage: ```bash cd apps/experiments/r2-storage npm install # 1. Create R2 buckets in Cloudflare Dashboard # - r2-storage-bucket (private) # - r2-storage-public (public) # 2. Enable public access on r2-storage-public: # Dashboard → R2 → r2-storage-public → Settings → Enable Public Access # Copy the public URL (e.g., https://pub-xxxxx.r2.dev) # 3. Update wrangler.json with bucket names # Set PUBLIC_BUCKET_URL in vars section # 4. Test with remote R2 (local mode doesn't support public URLs) npm run dev -- --remote # 5. Deploy npm run deploy ``` **wrangler.json structure:** ```json { "r2_buckets": [ { "binding": "BUCKET", "bucket_name": "r2-storage-bucket" }, { "binding": "PUBLIC_BUCKET", "bucket_name": "r2-storage-public" } ], "vars": { "PUBLIC_BUCKET_URL": "https://pub-xxxxx.r2.dev" } } ``` <Callout type="warning"> R2 requires the Workers Paid plan. Storage is charged per GB-month, with no egress fees. </Callout> </Tab> </Tabs> ## Custom Domains [#custom-domains] Add a custom domain to any deployed Worker: <Steps> <Step> ### Add domain to Cloudflare [#add-domain-to-cloudflare] Ensure your domain is already added to Cloudflare and using Cloudflare nameservers. </Step> <Step> ### Navigate to Workers & Pages [#navigate-to-workers--pages] In the Cloudflare dashboard: 1. Go to **Workers & Pages** 2. Select your deployed Worker 3. Click the **Triggers** tab </Step> <Step> ### Add custom domain [#add-custom-domain] 1. Click **Add Custom Domain** 2. Enter your subdomain (e.g., `api.example.com`) 3. Click **Add Custom Domain** Cloudflare automatically creates DNS records and provisions SSL certificates. </Step> </Steps> ## Environment Variables [#environment-variables] Some experiments require environment variables. Set them in the dashboard or via Wrangler: <Tabs items="['Dashboard', 'Wrangler CLI']"> <Tab value="Dashboard"> 1. Go to **Workers & Pages** 2. Select your Worker 3. Go to **Settings** → **Variables** 4. Add variables (Plain text or Secret) 5. Click **Save** </Tab> <Tab value="Wrangler CLI"> Set secrets via Wrangler: ```bash # Set a secret (encrypted) wrangler secret put API_KEY # Enter the value when prompted # Set a plain text variable (in wrangler.json) { "vars": { "PUBLIC_URL": "https://example.com" } } ``` </Tab> </Tabs> ## Deployment Checklist [#deployment-checklist] <Steps> <Step> ### Verify local functionality [#verify-local-functionality] ```bash npm run dev # Test all endpoints ``` </Step> <Step> ### Check TypeScript [#check-typescript] ```bash npm run build # or: tsc --noEmit ``` </Step> <Step> ### Create required resources [#create-required-resources] * D1 databases * KV namespaces * R2 buckets * Set environment variables </Step> <Step> ### Run migrations (if applicable) [#run-migrations-if-applicable] ```bash npm run db:migrate ``` </Step> <Step> ### Deploy [#deploy-1] ```bash npm run deploy ``` </Step> <Step> ### Test production [#test-production] ```bash curl https://your-worker.workers.dev ``` </Step> </Steps> ## Troubleshooting [#troubleshooting] ### Deployment Fails [#deployment-fails] * **Authentication error**: Run `wrangler login` to re-authenticate * **Missing binding**: Check `wrangler.json` for correct binding configuration * **TypeScript errors**: Run `npm run build` to identify issues ### Runtime Errors [#runtime-errors] * **AI binding not found**: Ensure Workers AI is enabled on your account * **Browser binding not found**: Enable Browser Rendering in your account * **Database not found**: Check D1 database\_id in `wrangler.json` * **KV not found**: Check KV namespace id in `wrangler.json` * **R2 not found**: Verify bucket names in `wrangler.json` ### Local Development Issues [#local-development-issues] * **AI doesn't work locally**: Use `npm run dev -- --remote` for Workers AI * **Browser doesn't work locally**: Use `npm run dev -- --remote` for Browser Rendering * **R2 public URLs don't work**: Use `npm run dev -- --remote` to test with real R2 ## Production Best Practices [#production-best-practices] <Cards> <Card title="Use Environment Variables"> Store sensitive values as secrets, not in code: ```bash wrangler secret put API_KEY ``` </Card> <Card title="Set Rate Limits"> Use Cloudflare's Rate Limiting to prevent abuse: Configure in the Cloudflare dashboard under Security. </Card> <Card title="Monitor Usage"> Track Worker invocations and errors: Workers & Pages → Your Worker → Metrics </Card> <Card title="Enable Analytics"> Use Workers Analytics for detailed insights: Available on Workers Paid plan </Card> </Cards> ## Next Steps [#next-steps] <Cards> <Card title="Explore Experiments" href="/"> Browse all available experiments </Card> <Card title="Architecture Patterns" href="/reference/architecture"> Learn common patterns used across experiments </Card> </Cards> # Experiment Documentation Guide (/docs/reference/experiment-docs) Every worker under `apps/experiments/<name>/` should have a matching docs page at `/experiments/<name>`. This guide explains structure, conventions, and the automation available to contributors and agents. ## Where docs live [#where-docs-live] | Artifact | Path | | ------------- | ---------------------------------------------------- | | MDX page | `apps/docs/content/docs/experiments/<name>.mdx` | | Sidebar order | `apps/docs/content/docs/meta.json` | | Docs app | `apps/docs/` (Fumadocs + Next.js) | | Worker source | `apps/experiments/<name>/` | | Root index | `README.md` experiments table (new experiments only) | Docs are **not** generated automatically on deploy. When you add or change an experiment, update its MDX page in the same PR (or immediately after). ## Quick start: new experiment [#quick-start-new-experiment] <Steps> <Step> ### Scaffold the MDX file [#scaffold-the-mdx-file] From the repository root: ```bash node apps/docs/scripts/scaffold-experiment-doc.mjs my-experiment ``` This creates `apps/docs/content/docs/experiments/my-experiment.mdx` with frontmatter, deploy button, and `TODO` markers derived from `src/index.ts` and `wrangler.json`. </Step> <Step> ### Fill in content from source code [#fill-in-content-from-source-code] Read these files (source of truth): 1. `src/index.ts` - name, description, usage 2. `src/routes/*.ts` - endpoints, validation, `jsonError` codes 3. `src/types/` - response shapes 4. `wrangler.json` - bindings 5. `README.md` - examples (verify against code) Replace every `TODO` in the MDX file. </Step> <Step> ### Add to sidebar navigation [#add-to-sidebar-navigation] Edit `apps/docs/content/docs/meta.json` and add `"experiments/my-experiment"` under the correct category (see [Categories](#categories) below). </Step> <Step> ### Update root README (new experiments only) [#update-root-readme-new-experiments-only] Add a row to the experiments table in the root `README.md` with name, description, and deploy link. </Step> <Step> ### Verify the docs build [#verify-the-docs-build] ```bash cd apps/docs && npm run build ``` Open `http://localhost:3000/experiments/my-experiment` after `npm run dev -- --filter=docs`. </Step> </Steps> ## Page structure [#page-structure] ### Required frontmatter [#required-frontmatter] ```yaml --- title: "Human Readable Name" description: "One sentence for SEO and page header (≤ ~160 characters)" --- ``` Use the same description as `GET /` returns in `src/index.ts` when possible. ### Required sections [#required-sections] 1. **Intro** - What the worker does and which Cloudflare product it showcases 2. **API Endpoint(s)** - One `### METHOD /path` block per public route 3. **Parameters** - Document each query/body field 4. **Example request** - `curl` against `https://your-worker.workers.dev` 5. **Response fields** - Type + description for each JSON key 6. **Example response** - Valid JSON 7. **Error responses** - `{ error, code }` shape with real codes from routes 8. **Use cases** - 3–5 bullets 9. **Deployment** - `<Steps>` with deploy button + test command 10. **Local development** - install, `npm run dev`, local `curl` on port `8787` 11. **Cloudflare features used** - Bullets with links to developer docs ### Optional sections [#optional-sections] Add when they help readers: | Section | When | | -------------------------- | ---------------------------------------------------- | | `<Callout type="warning">` | Experimental workers, AI cost/limits | | **Features** | Multiple capabilities beyond one endpoint | | **Implementation details** | AI pipelines, multi-step fetch/parse flows | | **Configuration** | Bindings, secrets, D1 schema SQL | | **Limitations** | Timeouts, size caps, local vs production differences | | **Next steps** | `<Cards>` to related experiments or Cloudflare docs | ### Reference examples [#reference-examples] * **Simple** (single route, no bindings): [Is It Down?](/experiments/is-it-down), [Where Am I?](/experiments/whereami) * **Full** (AI, config, implementation walkthrough): [AI Website Summary](/experiments/ai-website-summary) ## MDX conventions [#mdx-conventions] ### Parameter documentation [#parameter-documentation] Use bold name + type + description (Fumadocs prose style): ```mdx **`url`** `string` (required) The target URL to check. Must use `http://` or `https://`. ``` Do **not** use Mintlify components (`ParamField`, `Icon`, etc.). ### Deploy button [#deploy-button] Always use the monorepo path `apps/experiments/<name>`: ```mdx [![Deploy to Cloudflare Workers](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/shrinathsnayak/cloudflare-experiments/tree/main/apps/experiments/is-it-down) ``` ### Available MDX components [#available-mdx-components] Registered in `apps/docs/components/mdx.tsx`: * `Callout` - notes and warnings * `Steps` / `Step` - numbered procedures * `Cards` / `Card` - related links * `Accordions` / `Accordion` - collapsible detail External images (e.g. deploy button SVG) use plain `<img>` / markdown image syntax. ## Categories [#categories] Add new pages under the matching separator in `meta.json`: | Separator | Use for | | ------------------------------ | -------------------------------------------------- | | `---AI & Machine Learning---` | Workers AI, LLM summarization, AI proxies | | `---Web Scraping & Parsing---` | HTML fetch/parse, metadata, llms.txt, dependencies | | `---Browser & Screenshots---` | Browser Rendering binding | | `---Network & Monitoring---` | DNS, uptime, redirects, geolocation, `request.cf` | | `---Storage & Data---` | R2, D1, KV, link shorteners | If an experiment spans categories, pick the **primary** capability shown in its README and issue proposal. ## Updating existing docs [#updating-existing-docs] When changing an experiment: 1. Diff routes, types, `wrangler.json`, and README 2. Update only the affected MDX sections (API, errors, config, deployment notes) 3. Keep curl examples in sync with path and query names 4. Run `npm run build` in `apps/docs` Do not remove documented error codes that still exist in code, and do not document routes that were deleted. ## Accuracy checklist [#accuracy-checklist] Before merging: * [ ] Every endpoint in docs exists in `src/routes/` or `src/index.ts` * [ ] Error `code` strings match `jsonError(c, message, "CODE")` in source * [ ] Response JSON matches TypeScript types * [ ] Binding setup matches `wrangler.json` and `src/types/env.d.ts` * [ ] Page entry exists in `meta.json` * [ ] Deploy URL uses `apps/experiments/<name>` * [ ] `apps/docs` build passes ## Agent / Cursor skill [#agent--cursor-skill] The **`experiment-docs`** skill (`.cursor/skills/experiment-docs/SKILL.md`) instructs Cursor agents to: * Run the scaffold script * Follow the template at `.cursor/skills/experiment-docs/template.mdx` * Update `meta.json` and README * Verify the docs build Invoke it when adding or updating experiment documentation. ## Related guides [#related-guides] * [Adding New Experiments](/adding-experiments) - full worker scaffold process * [Code Standards](/code-standards) - API and folder conventions docs should reflect * [Contributing](/contributing) - PR expectations