Docs
Skip to content

PostgreSQL

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

Raw

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.

Use cases:

  • Preview environments: one branch per pull request, destroyed when the PR closes
  • Test migrations: apply a destructive ALTER against 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 ANALYZE experiments against a branch cannot slow down the primary

How it works

Creating a branch snapshots the parent's storage volume and provisions a branch instance from that snapshot, on the same engine version, with its own isolated storage. PostgreSQL snapshots are crash-consistent, so the parent is never frozen and takes no write pause. Branch compute is fixed and lightweight, enough to validate a change rather than carry production load, and is not configurable. Provisioning is asynchronous and usually takes a few minutes, because the storage provider finalizes the snapshot before the branch instance starts.

Create a branch

Both fields are optional:

FieldDefaultPurpose
branchIdauto-generatedCustom ID (a-z, A-Z, 0-9, ., -, _, max 36 chars)
ttl86400 (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. Every branch has a TTL. Leaving ttl out gives you 24 hours, and the longest branch you can create is 7 days, so a branch is never a permanent resource.

The value you send as branchId is stored as the branch name. The list response returns a separate generated branchId, so look up a branch by its name rather than by the value you passed.

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:

Bash
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 is billed for the parent's full provisioned storage from the moment it is created, pro-rated for how long the branch exists. Divergence is not measured, so a branch you never write to costs the same as one you rewrite completely. Short-lived branches are therefore much cheaper than long-lived ones. There is no separate branch line item, branches roll into your regular database storage totals.

Use case: a development copy of production

Branches also separate daily development from production without maintaining seed scripts. Create a 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.

Because a branch expires after 7 days at most, treat this as a recurring refresh rather than a standing environment. Delete the branch and create a new one with the same name on a schedule that suits your team. Each new branch starts from the parent's current state, which also keeps the development data from drifting far from production.

Use case: per-PR preview database

A CI pipeline that branches on every pull request and tears down on close:

YAML
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
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 }}

Branch creation does not deduplicate by name. Calling it twice with the same branchId provisions a second branch, so reopening a pull request leaves you with two. Delete the existing branch before recreating it, or list branches first and skip creation when one already matches.

Was this page helpful?

Share what worked or what we should fix. Once approved, our agents automatically apply suggested updates to the docs.