Branches_
Spin up an ephemeral, isolated copy of your PostgreSQL database in seconds from a storage snapshot. Use branches for previews, migrations, and testing.
3 min read
A branch is a short-lived, isolated copy of your database. It has its own endpoint and reuses the parent's credentials, because it is a snapshot copy of the parent's storage volume taken at a point in time. Branches are not replicas: once created, they diverge from the parent and never sync back.
There is no branch merge operation. Use a branch to validate a migration, data repair, or application change, then intentionally cut application traffic over to the validated database or copy the data you want back with engine-native tools. Appwrite does not reconcile two diverged database histories for you.
Use cases:
- Preview environments: one branch per pull request, destroyed when the PR closes
- Test migrations: apply a destructive
ALTERagainst the branch first, observe the behavior, then run it against the source - Reproduce a bug: branch the database, attach a debugger, throw the branch away when done
- Heavy analytical queries:
EXPLAIN ANALYZEexperiments against a branch cannot slow down the primary
How it works
Creating a branch takes a fast CHECKPOINT on the parent, snapshots the parent's storage volume with a near-instant copy-on-write operation, and provisions a branch instance from the snapshot on the same engine version with its own isolated storage. Branch compute is fixed and lightweight, enough to validate a change rather than carry production load, and is not configurable. The parent and the branch share storage at the moment of branching; storage cost grows as the two diverge.
Create a branch
Both fields are optional:
| Field | Default | Purpose |
|---|---|---|
branchId | auto-generated | Custom ID (a-z, A-Z, 0-9, ., -, _, max 36 chars) |
ttl | 86400 (24 hours) | Lifetime in seconds before the branch expires (min 300, max 604800) |
The call is asynchronous and returns immediately while the branch provisions in the background. When the TTL elapses, the branch and its storage are removed automatically.
List branches and connect
Each entry carries its metadata and connection details, so there is no separate credentials call:
A branch gets its own hostname, and reuses the parent's username and password because it is a snapshot copy of the parent's storage. The port is the standard 5432; branches have no connection pooler. Connect with the branch's connectionString straight from the response:
psql "<branch connectionString>"Delete a branch
Deleting a branch removes the branch's instance, its storage volume, and the underlying snapshot. There is no soft delete: once the branch is gone, the data is gone.
Billing
A branch runs on fixed, lightweight compute, so its cost is dominated by storage. The snapshot is free at the moment of branching; storage cost accumulates as the branch's data diverges from the parent. There is no separate branch line item, branches roll into your regular database storage and compute totals.
Use case: a development copy of production
Branches also separate daily development from production without maintaining seed scripts. Create a long-lived branch from the production database and point local and staging environments at the branch's hostname. Developers query production-shaped data, and every write stays on the branch, so production is never at risk from a bad migration or a careless DELETE.
Branch data diverges from the parent from the moment of branching. To refresh, delete the branch and create a new one with the same branchId; the new branch starts from the parent's current state. Set a ttl if the branch should clean itself up, or omit it for a permanent development copy.
Use case: per-PR preview database
A CI pipeline that branches on every pull request and tears down on close:
name: preview-database
on: pull_request: types: [opened, reopened, closed]
jobs: branch: if: github.event.action != 'closed' runs-on: ubuntu-latest steps: - name: Create branch run: | curl -X POST \ -H "X-Appwrite-Project: ${{ vars.APPWRITE_PROJECT_ID }}" \ -H "X-Appwrite-Key: ${{ secrets.APPWRITE_API_KEY }}" \ -H "Content-Type: application/json" \ -d '{"branchId": "pr-${{ github.event.number }}", "ttl": 604800}' \ https://<REGION>.cloud.appwrite.io/v1/postgresql/${{ vars.DATABASE_ID }}/branches || true
teardown: if: github.event.action == 'closed' runs-on: ubuntu-latest steps: - name: Delete branch run: | curl -X DELETE \ -H "X-Appwrite-Project: ${{ vars.APPWRITE_PROJECT_ID }}" \ -H "X-Appwrite-Key: ${{ secrets.APPWRITE_API_KEY }}" \ https://<REGION>.cloud.appwrite.io/v1/postgresql/${{ vars.DATABASE_ID }}/branches/pr-${{ github.event.number }}The || true on the create call makes the workflow idempotent: if the branch already exists, the call is a no-op.
Was this page helpful?
Share what worked or what we should fix. Once approved, our agents automatically apply suggested updates to the docs.