Database
Screenplay keeps everything durable in Postgres. Any Postgres works. The default driver is Neon’s serverless HTTP driver, and you can swap it.
Setup
- Create a database anywhere (Neon, Vercel Postgres, Supabase, your own
server) and put its connection string in
DATABASE_URL. - That’s all. The checked-in migrations in
apps/app/drizzle/are applied on every build (drizzle-kit migrate && next build), so each deploy upgrades the schema before the new code runs. To set up a local database, runpnpm db:migrateinapps/app.
What’s in the schema
The schema is split in two: apps/app/lib/db/schema-core.ts holds the tables
every build uses, and schema-multiuser.ts holds the tables only the hosted
build needs. schema.ts combines both.
Live canvas state (layout, frames, documents, chat tabs) lives in the Yjs host, not in Postgres.
Changing the schema
# 1. Edit apps/app/lib/db/schema-core.ts (or schema-multiuser.ts)
# 2. Generate the migration — a SQL file under apps/app/drizzle/
cd apps/app && pnpm db:generate
# 3. Commit the .sql file together with the schema change
# 4. The next deploy applies it (the build runs `drizzle-kit migrate`)Migrations are idempotent: already-applied ones are skipped. For throwaway
local experiments, pnpm db:push syncs the schema without a migration file.
Don’t commit the result.
Preview deploys run migrations against whatever DATABASE_URL their scope
has. Point previews at a separate database, or a PR’s migration reaches
production before it merges. See Deploying to
Vercel.
Using a different Postgres driver
The app programs against the DB type in apps/app/lib/db/types.ts, which is
Drizzle’s generic PgDatabase. selectDb() in apps/app/lib/db/index.ts
chooses the concrete driver: Neon’s serverless HTTP driver by default, or
embedded PGlite when SCREENPLAY_DB=pglite (the desktop build). To use a
different driver:
-
Install it (
postgres,pg,@vercel/postgres, …). -
Add a sibling factory, for example
apps/app/lib/db/postgres-js.ts:import postgres from "postgres" import { drizzle } from "drizzle-orm/postgres-js" import * as schema from "./schema" import type { DB } from "./types" export function createPostgresJsDb(): DB { if (!process.env.DATABASE_URL) throw new Error("DATABASE_URL is not set") return drizzle(postgres(process.env.DATABASE_URL), { schema }) } -
Return it from
selectDb()in place ofcreateNeonDb(), or behind a newSCREENPLAY_DBvalue.
Everything else, including lib/kv, uses the same handle and needs no changes.