Cloudflare D1 in ten minutes
From zero to a working SQLite database on the edge, queried from a Next.js app. Copy-pasteable.
Yunzhui Cai
Published May 10, 2026
D1 is Cloudflare's SQLite-based serverless database. We use it inside Orpheus to store per-tenant transcription metadata, and we use it on Signal (this site) to store newsletter subscribers. This post takes you from wrangler login to running your first query.
Step 1 — create the database
npx wrangler d1 create my-db
Save the database_id it prints. Paste it into wrangler.toml:
[[d1_databases]]
binding = "DB"
database_name = "my-db"
database_id = "00000000-0000-0000-0000-000000000000"
Step 2 — define a schema
CREATE TABLE subscribers (
email TEXT PRIMARY KEY,
locale TEXT NOT NULL,
created_at INTEGER NOT NULL
);
Save as migrations/0000_init.sql, then:
npx wrangler d1 migrations apply my-db --remote
Step 3 — query
const { results } = await env.DB
.prepare("SELECT * FROM subscribers WHERE locale = ?")
.bind(locale)
.all();
That's it. No connection pool, no migrations server, no on-call rotation.
When D1 fits and when it doesn't
Fits: Per-tenant data, user lists, small to medium analytics, anything that benefits from being co-located with your Worker. Up to about a gigabyte per database before you start thinking about sharding.
Doesn't fit: Multi-region writes with strong consistency. Anything beyond ~50GB. Full-text search at scale (use Vectorize or a dedicated service).
For Signal's newsletter list, D1 is comically over-spec'd. That's fine — it'll never break.
Continue reading