Database
Every Worker app gets its own SQL database: a Cloudflare D1 database, which is SQLite, bound to your code as DB. It’s made on the app’s first deploy, belongs to the app rather than a version, and only that app can reach it.
Which apps have one
Section titled “Which apps have one”| Kind | Database |
|---|---|
| Worker, including a framework build | Yes, from its first deploy. |
| Static site | No. |
| Container | No. |
Use it from a Worker
Section titled “Use it from a Worker”env.DB is a D1 database. Your code creates its own tables:
export default { async fetch(request, env) { await env.DB.exec("CREATE TABLE IF NOT EXISTS notes (id INTEGER PRIMARY KEY, author TEXT, body TEXT)");
if (request.method === "POST") { await env.DB.prepare("INSERT INTO notes (author, body) VALUES (?, ?)") .bind("someone@example.com", await request.text()) .run(); }
const { results } = await env.DB.prepare("SELECT author, body FROM notes ORDER BY id").all(); return Response.json(results); },};In a real app, take the author from the caller the SDK verified rather than from the request.
In TypeScript, declare it on your Env:
interface Env { DB: D1Database;}A wrangler config may name the binding, for your own tools’ sake, as long as it’s called DB. The platform binds the app’s own database there whatever the config’s database_id says. Any other name is refused:
error: wrangler.jsonc binds D1 as MAIN_DB; the platform gives each app one database, bound as DB.What happens to the data
Section titled “What happens to the data”- Deploys and rollbacks leave it alone. Every version of the app talks to the same database, so rolling back changes the code, not the data.
- Deleting the app deletes the database with it, straight away. Nothing is restorable, so export it first.
How big it can get
Section titled “How big it can get”The database counts toward the workspace’s storage, with the site files its apps serve: 1 GB on Free, 10 GB on Team and 100 GB on Business, for everything together. It’s measured about once an hour.
Writes are never refused for it. Once the workspace is past its storage, though, nothing new deploys in it until it’s back under: delete rows, or move to a bigger plan. Cloudflare’s own limit is 10 GB for one database.
From 1 November 2026
Section titled “From 1 November 2026”The workspace’s databases together also get a limit of their own, inside the storage: 1 GB on Free, 2 GB on Team and 10 GB on Business. They’re measured every few minutes. Over the limit, apps with a database answer only the workspace’s owners and admins, with 507 for everyone else, and past 10% over they answer no one. On Free, a workspace that stays over is suspended after 7 days, and its largest databases are deleted 30 days after that, after email notice. Limits has the whole of it.
Free also stops for the month at 50,000,000 rows read or 100,000 written across its databases. Cloudflare counts a row for every row a query scans, not only the ones it returns, so an index on the columns you filter by keeps the count down.
Run a statement
Section titled “Run a statement”jiayang db exec runs one SQL statement against an app’s database. An owner can run any statement; an admin can run a DELETE, a DROP TABLE or a DROP INDEX, since a read sees everything in the database and only an owner may export it:
jiayang db exec acme/notes "DELETE FROM notes WHERE created < '2026-01-01'"It takes one statement at a time, and each run goes in the audit log. While the workspace’s databases are over its limit, or a Free workspace is paused for its database rows, it takes only a DELETE, a DROP TABLE or a DROP INDEX from anyone, so you can get back under, and the database is measured again straight after. The log records the kind of statement and a hash of it, never its text.
Rows a statement reads and writes count toward Free’s monthly rows like any other query. Whether a DELETE shrinks the database’s size at once is up to D1; if the size doesn’t come down, export what you want to keep, delete the app’s data, and load it back.
Export it
Section titled “Export it”jiayang db export writes the whole database as a SQL dump, ready to load into SQLite or another D1 database:
jiayang db export acme/notesExported acme/notes's database to acme-notes.sql (48213 bytes).The dump is plain SQL:
head -3 acme-notes.sqlPRAGMA defer_foreign_keys=TRUE;CREATE TABLE notes (id INTEGER PRIMARY KEY, author TEXT, body TEXT);INSERT INTO "notes" ("id","author","body") VALUES(1,'someone@example.com','first note');-o names the file, and --stdout writes the dump to stdout instead:
jiayang db export acme/notes -o backup.sqljiayang db export acme/notes --stdout | sqlite3 notes.dbThe file is written beside its target and moved into place once the dump is whole, so a failed export never leaves a file that looks complete.
The dashboard has the same export under the app’s Settings tab, as Database export.
Only the workspace’s owners can export a database. Anyone else is refused:
error: You don't have permission to do that.One export of an app runs at a time:
error: This app's database is being exported already. Try again in a minute.A static site, a container, or a Worker app that hasn’t been deployed yet has nothing to export:
error: This app has no database to export.Each export goes in the audit log.
- Versions and rollback to put earlier code back without touching the data.