# 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]
[](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
[](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.
[](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]
[](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]
[](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]
[](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:
### 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");
```
### Fetch HTML [#fetch-html]
Retrieves the webpage with a 15-second timeout and 128KB size limit:
```typescript
const html = await fetchHtml(url);
```
### 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
```
### 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
### 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);
```
### 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(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]
### Click the deploy button [#click-the-deploy-button]
[](https://deploy.workers.cloudflare.com/?url=https://github.com/shrinathsnayak/cloudflare-experiments/tree/main/apps/experiments/ai-website-summary)
### 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/summary?url=https://www.cloudflare.com"
```
## 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]
Explore other models available in Workers AI
Learn more about the Hono web framework
View the complete source code
Explore other Cloudflare experiments
# AI Website Tag Generator (/docs/experiments/ai-website-tag-generator)
This is an experimental Worker. Use it as a starting point for your own projects.
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.
#### 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]
### Click the deploy button [#click-the-deploy-button]
[](https://deploy.workers.cloudflare.com/?url=https://github.com/shrinathsnayak/cloudflare-experiments/tree/main/apps/experiments/ai-website-tag-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/tags?url=https://www.cloudflare.com"
```
## 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]
Explore other models available in Workers AI
Summarize webpages with Workers AI
View the complete source code
Explore other Cloudflare experiments
# 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.
#### 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]
### Click the deploy button [#click-the-deploy-button]
[](https://deploy.workers.cloudflare.com/?url=https://github.com/shrinathsnayak/cloudflare-experiments/tree/main/apps/experiments/analytics-engine)
### 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.
### 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"}'
```
## 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]
### Click the deploy button [#click-the-deploy-button]
[](https://deploy.workers.cloudflare.com/?url=https://github.com/shrinathsnayak/cloudflare-experiments/tree/main/apps/experiments/api-mock-server)
### Configure bindings [#configure-bindings]
Create a KV namespace and set its id in `wrangler.json` under `MOCKS`.
### Test your deployment [#test-your-deployment]
See the experiment README for curl examples.
## 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]
#### 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]
### Click the deploy button [#click-the-deploy-button]
[](https://deploy.workers.cloudflare.com/?url=https://github.com/shrinathsnayak/cloudflare-experiments/tree/main/apps/experiments/broken-link-checker)
### Deploy [#deploy]
Follow the deployment wizard. No bindings or secrets required.
### Test your deployment [#test-your-deployment]
```bash
curl "https://your-worker.workers.dev/check?url=https://example.com"
```
## 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]
Extract links from JavaScript-rendered pages
Check if a single URL is reachable from the edge
# 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]
#### 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]
### Click the deploy button [#click-the-deploy-button]
[](https://deploy.workers.cloudflare.com/?url=https://github.com/shrinathsnayak/cloudflare-experiments/tree/main/apps/experiments/browser-links)
### Enable Browser Rendering [#enable-browser-rendering]
Browser Rendering must be enabled on your account.
## Local Development [#local-development]
```bash
cd apps/experiments/browser-links
npm install
npm run dev
```
`npm run dev` uses `wrangler dev --remote` so a real browser instance is available.
## 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)
This is an experimental Worker. Use it as a starting point for your own projects.
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.
The endpoint is public by design. For production use, add rate limiting or API key authentication.
## 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`.
#### 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]
### Click the deploy button [#click-the-deploy-button]
[](https://deploy.workers.cloudflare.com/?url=https://github.com/shrinathsnayak/cloudflare-experiments/tree/main/apps/experiments/cloud-ai-proxy)
### 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 -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."}'
```
## 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]
# 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]
### Click the deploy button [#click-the-deploy-button]
[](https://deploy.workers.cloudflare.com/?url=https://github.com/shrinathsnayak/cloudflare-experiments/tree/main/apps/experiments/cors-preflight-tester)
### Configure bindings [#configure-bindings]
See `wrangler.json` and the experiment README for required bindings.
### Test your deployment [#test-your-deployment]
See the experiment README for curl examples.
## 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
}
```
Before the first cron fires, `lastRun` and `lastCron` are `null` and `runCount` is `0`.
## 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]
### Click the deploy button [#click-the-deploy-button]
[](https://deploy.workers.cloudflare.com/?url=https://github.com/shrinathsnayak/cloudflare-experiments/tree/main/apps/experiments/cron-heartbeat)
### Create KV namespace [#create-kv-namespace]
Update `wrangler.json` with your KV namespace ID for the `STATUS` binding.
### Check status [#check-status]
```bash
curl "https://your-worker.workers.dev/status"
```
## 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.
#### 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]
### Click the deploy button [#click-the-deploy-button]
[](https://deploy.workers.cloudflare.com/?url=https://github.com/shrinathsnayak/cloudflare-experiments/tree/main/apps/experiments/crypto-hash)
### Deploy [#deploy]
No additional configuration required.
### Test your deployment [#test-your-deployment]
```bash
curl "https://your-worker.workers.dev/hash?text=hello"
```
## 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.
#### 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]
### Click the deploy button [#click-the-deploy-button]
[](https://deploy.workers.cloudflare.com/?url=https://github.com/shrinathsnayak/cloudflare-experiments/tree/main/apps/experiments/d1-sql-playground)
### Configure D1 [#configure-d1]
Create a D1 database, set `database_id` in `wrangler.json`, then apply migrations:
```bash
npm run db:migrate
```
### 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"}'
```
## 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.
#### 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 `