Skip to content

Forking

A fork is a brand-new database, in the same workspace, that copies the source’s schema — tables, indexes, triggers, and views — into empty tables. Rows are not copied: the fork starts empty. Once forked, the two databases are fully independent — writes to one don’t affect the other. Seed the fork with fixtures or a migration.

In the console: Database → Fork in the page header. Pick a name and slug for the fork; PerSQL provisions the new database and replays the source’s schema into it.

Forked databases remember where they came from. The detail page shows a “Forked from <source-slug> · 3d ago” breadcrumb that links back to the parent. The lineage stays even if the parent is later deleted (the link goes dead, but the label remains).

The same fields are exposed on the API:

db.forkedFrom; // { id, slug, name } | null
db.forkedAt; // ISO string | null

Only GET /api/namespaces/:ns/databases/:slug resolves the parent’s slug+name; the workspace list endpoint returns just the raw forkedAt to keep the response cheap.

  • Tables, indexes, triggers, and views — the full schema (every DDL object in sqlite_master).

Rows are not copied. The fork starts with the same structure and empty tables; seed it with fixtures or a migration.

  • Saved queries (per-database, scoped to source).
  • Migrations history (per-database).
  • Schedules (per-database).
  • Custom hostnames.
  • API tokens (those are per-workspace and apply to the fork too).
  1. PerSQL reads the source’s schema — the CREATE TABLE/INDEX/TRIGGER/VIEW statements — not its rows.
  2. A new database row is inserted into the workspace registry with status provisioning.
  3. A fresh database is initialized at the new id and the schema is replayed into it (one transaction).
  4. On success, the new database is flipped to healthy.
  5. On failure, the new database row is cleaned up and the error is surfaced to the user.

Forks can carry a self-destruct timer. Pass ttlDays (1–30) on the REST call, or --ttl 7d on the CLI, and the daily 04:00 UTC cron deletes the fork — the database and its registry row — once the timestamp passes. Pre-existing forks without a TTL are unaffected.

persql db fork acme/orders pr-142 --ttl 7d

The detail page shows an “Auto-deletes in 6d” line; the database list shows an “expires in 6d” badge that turns red under 6 hours.

For PR-style ephemeral databases, the Branches API is usually a better fit: it’s idempotent (PUT-by-ref creates or resets), so CI doesn’t need to track whether the database already exists.

POST /api/namespaces/:ns/databases/:db/fork
{ "name": "Orders staging", "slug": "orders-staging", "region": "auto", "ttlDays": 7 }

ttlDays is optional. Manage permission required on the source. The response is the new database row, including its expiresAt field.

  • A fork copies schema only, so its cost is bounded by the number of schema objects, not the parent’s data size — forking a large database is as cheap and fast as forking an empty one.
  • The fork starts with empty tables. If you need the parent’s data, load it explicitly (fixtures, a migration, or Import SQL).
  • Cross-workspace fork is not supported in v1 — fork within the workspace, then transfer if needed.