This site is not affiliated with or endorsed by Cloudflare, Inc. It simply showcases experiments built using Cloudflare services.
Cloudflare Experiments
Storage & Data

D1 SQL Playground

Read-only SQL playground over a seeded D1 database with safe SELECT validation

Run safe, read-only SELECT queries against a seeded Cloudflare D1 database. Returns JSON rows, inferred column metadata, and query timing.

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

POST /query

Execute a single read-only SELECT against the seeded database.

Prop

Type

Example Request

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

{
  "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

  • 400 - Invalid JSON (INVALID_BODY) or disallowed SQL (INVALID_SQL)
  • 502 - D1 execution error (QUERY_ERROR)

GET /

Returns app metadata and the list of allowed tables (products, experiments).

Seeded Tables

TableColumns (high level)
productsid, name, category, price, in_stock, created_at
experimentsslug, name, category, description

Example queries:

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

  • 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

  • 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)

Use in your project

Copy these files into an existing Worker. Prefer Deployment to try the full experiment first. Source: apps/experiments/d1-sql-playground.

DependenciesNoneBindingsD1PlatformWorkers runtime
import type { ColumnMeta, QueryResponse } from "../types/query";import { inferColumnType } from "./sql";export async function runSelectQuery(db: D1Database, sql: string): Promise<QueryResponse> {  const started = Date.now();  const result = await db.prepare(sql).all<Record<string, unknown>>();  const rows = result.results ?? [];  return {    columns: buildColumnMetadata(rows),    rows,    rowCount: rows.length,    durationMs: Date.now() - started,  };}function buildColumnMetadata(rows: Record<string, unknown>[]): ColumnMeta[] {  if (rows.length === 0) {    return [];  }  return Object.keys(rows[0]).map((name) => ({    name,    type: inferColumnType(rows[0][name]),  }));}

Deployment

Click the deploy button

Deploy to Cloudflare Workers

Configure D1

Create a D1 database, set database_id in wrangler.json, then apply migrations:

npm run db:migrate

Test your deployment

curl -X POST "https://your-worker.workers.dev/query" \
  -H "Content-Type: application/json" \
  -d '{"sql":"SELECT * FROM products LIMIT 3"}'

Local Development

cd apps/experiments/d1-sql-playground
npm install
npm run db:migrate:local
npm run dev

Test locally:

curl -X POST "http://localhost:8787/query" \
  -H "Content-Type: application/json" \
  -d '{"sql":"SELECT * FROM experiments LIMIT 5"}'

Configuration

BindingPurpose
DBD1 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

  • Workers - Edge compute runtime
  • D1 - SQLite database at the edge with seeded read-only playground data

On this page