Preview branches: a separate database, URL and keys for every feature branch or pull request, built from your migrations.
A preview branch is a separate project that belongs to a production project: its own Postgres database, API URL, keys, Auth users, storage and functions. It starts from production's migrations (or, when production has none yet, its current schema), then runs your seed script. Data is not copied unless you ask: either production's data with personal data masked (below), or a branch you create from Backups › Restore to a point in time › A new branch, which starts with production's data as it was at that moment (see Backups).
| Plans | Pro: 3 branches per project. Scale: 10. Not on Free. |
| Schema | Production's migrations in order (or a copy of its schema), then seed.sql |
| Settings | Auth settings (providers, redirect URLs, e-mails) are copied from production |
| Limits and usage | The same limits as your plan; branches count towards your account's shared database and file quota |
| Idle branches | Pause after 3 days without requests; restore them from their page. They are not deleted for inactivity. |
| Deleting | Delete a branch any time; deleting the production project deletes its branches |
#Create a branch
From the project's Branches page, or with the CLI:
stitchdb branches create feature/checkout --wait
stitchdb branches switch feature/checkout # db push, functions deploy, gen types now use the branch
stitchdb db push
stitchdb branches switch mainThe branch switcher under the project name in the dashboard moves between production and its branches.
#Production data, masked
To test with realistic data, choose Copy production data, masked when you create the branch (or stitchdb branches create <name> --with-data). After the migrations, the branch gets production's rows with personal data replaced by fake values:
- Masking runs inside production's own query, so real values never leave production.
- Columns are masked by name and type: e-mails, phone numbers, first, last and full names (a plain
namecolumn in tables such asusers,customersorprofiles), usernames, street addresses, cities, postal codes, IP addresses, birth dates (shifted by up to 180 days), and passwords, tokens and API keys (removed). Identity numbers such asibanortax_idbecome random digits. - The same value gets the same fake value everywhere in one copy: the e-mail in
auth.users,customersandordersstays equal, so joins and unique keys keep working. Each new branch gets different fake values. auth.usersandauth.identitiescome along masked: e-mail, phone, names in the profile metadata (other metadata is dropped) and every token. Passwords are removed. With a test password (--test-password), every copied user can sign in with it. Sessions, refresh tokens, MFA factors, audit logs and Storage files are not copied.- Rows are loaded without firing triggers or foreign key checks (they were consistent in production), sequences continue where production is, and materialized views are refreshed.
Mark more columns, or keep one as it is, from Branches › Masking rules, or in SQL so the rule lives in your migrations:
comment on column public.tickets.body is '@mask text'; -- placeholder text
comment on column public.orders.gift_note is '@mask null'; -- removed| Rule | Becomes |
|---|---|
email, first_name, last_name, full_name, username | A fake e-mail at example.com or a fake name |
phone, street, city, zip, ip | A fake phone number (+1 555…), address, city, postal code or private IP address |
date | The date shifted by up to 180 days |
text, hash, digits, empty, null | Placeholder text, a hash (also for uuid), random digits, an empty value or NULL |
keep | Unchanged (turns off automatic masking for that column) |
Rules in the dashboard win over column comments, which win over the automatic choice. Masking is a safeguard for test environments, not anonymisation in the legal sense: free-text and JSON columns can still contain personal data, so check the preview in Masking rules and add rules for them. The copy is limited to the size of production's tables by plan (Data copied into a preview branch or a new project); a branch counts towards your database size like any project.
#Merge
Merging applies the migrations the branch has and production doesn't, in order, on production. Data is not merged.
stitchdb branches merge feature/checkout --dry-run
stitchdb branches merge feature/checkout --deleteWith the GitHub App or the GitHub workflow, merging the pull request does this for you: the commit on main pushes the migrations to production and the branch is deleted when the pull request closes.
#API
| Method and path | Does |
|---|---|
GET /platform/v1/projects/<ref>/branches | List branches |
POST /platform/v1/projects/<ref>/branches | Create: { git_branch, seed?, data?: "masked", test_password? } (returns the existing branch when the name is taken) |
GET /platform/v1/projects/<ref>/branches/masking/preview | Tables, sizes and the columns a masked copy replaces |
PUT /platform/v1/projects/<ref>/branches/masking | Rules: { rules: { "public.orders.note": "text" } } |
GET /platform/v1/branches/<branch-ref> | Status: branch_status is creating, ready or failed |
POST /platform/v1/branches/<branch-ref>/merge | { dry_run?, delete? } |
DELETE /platform/v1/branches/<branch-ref> | Delete the branch |