Database
Each app comes with its own SQL database, exposed as env.DB. It's SQLite at the edge, with the standard prepared-statement API. You define the schema; the platform reserves a small set of table names for its own managed features.
Querying with env.DB
Use prepared statements with bound parameters — never string interpolation:
// api/notes.js
export default {
async fetch(request, env, ctx) {
const user = await env.AUTH.getUser(request);
if (!user) return new Response("Unauthorized", { status: 401 });
// read many rows
const { results } = await env.DB
.prepare("SELECT id, body, created_at FROM notes WHERE user_id = ?")
.bind(user.id)
.all();
return Response.json(results);
},
};The common methods:
.prepare(sql).bind(...args)— build a parameterized statement..all()— all rows ({ results })..first()— the first row, ornull..run()— execute a write (insert / update / delete).env.DB.batch([...statements])— run several statements atomically.
Schema & migrations
Define and evolve your schema with SQL files under migrations/. Keep them ordered (a numeric prefix is the convention) so they apply deterministically:
-- migrations/0001_notes.sql
CREATE TABLE IF NOT EXISTS notes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id TEXT NOT NULL,
body TEXT NOT NULL,
created_at INTEGER NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_notes_user ON notes(user_id);You can review what an app has defined with the list_migrations MCP tool. In practice you just ask your assistant for the schema you want and it writes the migration.
Reserved: the _cr_* namespace
Table names starting with _cr_ are owned by the platform — they back managed features like env.AUTH (_cr_users, _cr_sessions) and your app's own MCP OAuth server (_cr_oauth_*). They're created lazily on first use.
_cr_* tables yourself — name your own tables anything else. Use the env.AUTH methods to read or change user data.Preview vs production data
A production (main) deploy uses the real app database. Branch and preview deploys run against a separate throwaway database, so work-in-progress can't corrupt live data — see Branches, PRs & AI merges.