Database
A real Postgres database per app — migrations, a schema editor, an isolated branch per preview, export and restore.
Every Camplax project gets its own Postgres database. It is real Postgres — not a proprietary layer with SQL painted on — so any Postgres client, ORM or migration tool you already know still works. It is always on, and it is yours alone: no other project can reach it.
Two connection strings
Your app receives two:
| Variable | What it is for |
|---|---|
DATABASE_URL | The pooled connection — what the app uses at runtime, built for many short server connections |
DIRECT_URL | The direct connection — for migrations, admin tools and psql, which need a session of their own |
Both are injected by the platform and live in the vault, so you never paste either into code. The practical rule: the app reads DATABASE_URL; prisma migrate, psql and other long-lived clients use DIRECT_URL. See Secrets for how they are injected.
The console database page
camplax db studio opens it, or find Database in the console sidebar. Five things live there:
- Schema — every table, column, type and relation, read live from the database.
- Data — a grid over any table: browse with sorting and pagination, add rows, edit cells, delete rows.
- Migrations — the apply history: each schema change recorded as a named snapshot with its SQL.
- SQL — a runner for hand-written queries, behind a guard that caps reads and asks before writes.
- Branches — the isolated databases that back previews, with create, reset and delete.
There is also a copilot: ask a question in English and it writes the SQL against your real schema — review the statement, then run it. And a seed button fills a table (and the empty parents it points at) with believable rows for testing.
Migrations
Schema changes live in your repo, next to the code that needs them. camplax db migrate applies local SQL files to the project database. It looks in the usual places:
prisma/migrations/<name>/migration.sql
drizzle/*.sql
sql/migrations/*.sql
migrations/*.sql
The app also runs its checked-in SQL at boot — applied on a fresh database, verified on a rollout — and a deploy only reports ready after a real read/write probe against the database has passed, so the schema and the app cannot drift apart silently.
Editing the schema without SQL files
The schema tab doubles as an editor. Change the schema there and Camplax computes the diff, shows you the exact SQL it will run, and warns you about anything destructive or table-locking. Applying runs the statements as one transaction — a failure halfway leaves the schema exactly as it was. Anything that loses data needs a second, explicit confirmation, and each apply lands in the history list as a named snapshot (the last 50 are kept). One thing the editor cannot guess: renaming a column and dropping-plus-adding one look identical afterwards, so it asks you to say which you meant.
camplax gen types # writes database.types.ts from the live schema
camplax gen types --out db-types.ts
A database branch per preview
A preview never sees production data. Every preview runs against a database branch — a separate, isolated database on the same project. Branches are created automatically when a pull request opens on a connected repo, or by hand on the Database page / POST /v1/projects/:slug/database/branches. A preview deploy will not start without one; it fails rather than silently fall back to production.
Two things to know plainly:
- A branch starts empty — unless you ask for production data. Branches are empty by default; if you want real data in a preview, say so when you create it (
camplax deploy --branch <name> --with-data, or Recreate with production data on the Database page's branches tab). It is a raw copy of production — rows, tokens and personal data come along verbatim, nothing is sanitized — so treat a data-carrying preview like production for access purposes. - A branch is disposable. Reset recreates it empty; closing the PR deletes it along with the rest of the preview. See Previews.
Point-in-time restore
Every database keeps a continuous backup (a WAL archive) for 7 days. You can restore the production database to any moment inside that window:
- On the Database page's branches tab, pick a date and time under Restore to a point in time.
- Or
POST /v1/projects/:slug/database/pitrwith{ "at": "<RFC 3339 timestamp>" }— e.g.{ "at": "2026-09-22T14:30:00Z" }.
The restore lands as a new, separate environment — it never overwrites production. It appears under the branches list (branch name restore-<timestamp>), its database holds production's data as of that moment, and you can inspect it in the studio by picking it from the environment selector at the top of the Database page. When you are done with it, tear it down from the same list.
Two honest limits: the earliest restore point is the first completed daily base backup inside the window — a database younger than its first backup cannot rewind; and a restore of a large database can take a while, the environment shows as provisioning until the copy is queryable.
Export and restore
You can take your data with you:
- Export —
GET /v1/projects/:slug/settings/export/databasedownloads apg_dumpfile of your database. Owner-only, and unavailable to project API keys: it is every row you have. - Restore a file —
POST /v1/projects/:slug/settings/restore/databaseaccepts a.dumpfile up to 20 MB and restores it into a new, isolated database attached to the project. It does not overwrite your live database — you inspect the restored copy and decide what to do with it. For rewinding to a moment in time instead of a file, use point-in-time restore above.
Rows stay with their owners
Access control lives in the app, not in a separate rules engine. In a Plax app — including the camplax create starter — every table is wrapped in defineResource with an ownerField, and each read, update and delete it serves is scoped to the signed-in user's id inside the query itself. The client cannot set that field; the server fills it from the session, and a row you cannot see answers 404. The starter's notes resource is the pattern to copy for new tables.
One honest limit
There is no SQL-over-HTTP endpoint for app code: the SQL runner and copilot are console surfaces for you, not an API your app calls. Your app talks to its database over the normal Postgres protocol through DATABASE_URL.
Under the hood: one Postgres database per environment on the app's cell, always on, behind the injected DATABASE_URL — with WAL continuously archived for the point-in-time window above.