How to Branch Your Database

Open inAnthropic

What you'll achieve

By the end of this guide, you will know how to create, switch, diff, merge, and delete branches in TerminusDB using the HTTP API, TypeScript, and Python.

This guide shows you how to work with branches in TerminusDB — create isolated workspaces, make changes, review diffs, and merge back to main. Every operation includes HTTP API, TypeScript, and Python examples.

Git branch for data

Branching in TerminusDB works exactly like git branch — create a lightweight branch, make changes in isolation, then merge back when ready. Branches share history until they diverge, making them cheap to create.

Prerequisites

  • TerminusDB running on localhost:6363 — see installation guide
  • A database with a schema. Examples use admin/tdb-example-mydb with a Product class. Set up:

Example database

Connect to TerminusDB first.

# Create the database
curl -u admin:root -X POST http://localhost:6363/api/db/admin/tdb-example-mydb \
  -H "Content-Type: application/json" \
  -d '{"label": "My Database"}'

# Add a Product schema class
curl -u admin:root -X POST \
  "http://localhost:6363/api/document/admin/tdb-example-mydb?graph_type=schema&author=setup&message=Add+schema" \
  -H "Content-Type: application/json" \
  -d '{"@type": "Class", "@id": "Product", "name": "xsd:string", "price": "xsd:decimal", "category": "xsd:string"}'

1. Create a new branch

Create a branch from the current head of main. The new branch shares all existing history and diverges from this point forward.

HTTP API

curl -u admin:root -X POST http://localhost:6363/api/branch/admin/tdb-example-mydb/local/branch/feature \
  -H "Content-Type: application/json" \
  -d '{"origin": "admin/tdb-example-mydb/local/branch/main"}'

Expected response:

Example: JSON
{"@type":"api:BranchResponse","api:status":"api:success"}
Example: TypeScript
import TerminusClient from "@terminusdb/terminusdb-client";

const client = new TerminusClient.WOQLClient("http://localhost:6363", {
  user: "admin",
  key: "root",
  organization: "admin",
  db: "tdb-example-mydb",
});

await client.branch("feature");
// Branch "feature" created from current head of main

2. List all branches

See all branches in your database.

HTTP API

curl -u admin:root "http://localhost:6363/api/db/admin/tdb-example-mydb?branches=true"

Expected response:

Example: JSON
{"path": "admin/tdb-example-mydb", "branches": ["feature", "main"]}
Example: TypeScript
// List branches by fetching db info
const info = await client.getDatabase("tdb-example-mydb");
console.log(info.branches);
// ["feature", "main"]

3. Switch to a branch

There is no "checkout" command — you simply target a different branch path in your API calls. Every TerminusDB API path includes the branch:

  • main: /api/document/admin/tdb-example-mydb/local/branch/main
  • feature: /api/document/admin/tdb-example-mydb/local/branch/feature

HTTP API

# Read documents from main
curl -u admin:root "http://localhost:6363/api/document/admin/tdb-example-mydb/local/branch/main?type=Product&as_list=true"

# Read documents from feature branch — just change the path
curl -u admin:root "http://localhost:6363/api/document/admin/tdb-example-mydb/local/branch/feature?type=Product&as_list=true"
Example: TypeScript
// Switch branch context for all subsequent operations
client.checkout("feature");

// All operations now target the feature branch
const docs = await client.getDocument({ type: "Product", as_list: true });

4. Make changes on a branch

Insert or update documents on your branch. Changes are isolated — main is unaffected until you merge.

HTTP API

# Insert a new document on the feature branch
curl -u admin:root -X POST \
  "http://localhost:6363/api/document/admin/tdb-example-mydb/local/branch/feature?author=alice&message=Add+Widget+product" \
  -H "Content-Type: application/json" \
  -d '{"@id": "Product/Widget", "@type": "Product", "name": "Widget", "price": 9.99, "category": "tools"}'

Expected response:

Example: JSON
["terminusdb:///data/Product/Widget"]
# Update an existing document on the feature branch
curl -u admin:root -X PUT \
  "http://localhost:6363/api/document/admin/tdb-example-mydb/local/branch/feature?author=alice&message=Update+Widget+price" \
  -H "Content-Type: application/json" \
  -d '{"@id": "Product/Widget", "@type": "Product", "name": "Widget", "price": 14.99, "category": "tools"}'

Expected response:

Example: JSON
["terminusdb:///data/Product/Widget"]
Example: TypeScript
client.checkout("feature");

// Insert
await client.addDocument(
  { "@type": "Product", "name": "Widget", "price": 9.99, "category": "tools" },
  { author: "alice", message: "Add Widget product" }
);

// Update
await client.updateDocument(
  { "@id": "Product/Widget", "@type": "Product", "name": "Widget", "price": 14.99, "category": "tools" },
  { author: "alice", message: "Update Widget price" }
);

5. Compare a branch to main (diff)

See exactly what changed on your branch compared to main — a structural, field-level diff.

HTTP API

curl -u admin:root -X POST http://localhost:6363/api/diff/admin/tdb-example-mydb \
  -H "Content-Type: application/json" \
  -d '{"before_data_version": "main", "after_data_version": "feature"}'

Expected response:

Example: JSON
[
  {
    "@insert": {
      "@id": "Product/Widget",
      "@type": "Product",
      "name": "Widget",
      "price": 14.99,
      "category": "tools"
    },
    "@op": "Insert"
  }
]

The diff shows typed operations:

  • Insert — a new document exists on the branch but not on main
  • Delete — a document exists on main but not on the branch
  • SwapValue — a field value changed (when the document already existed on both branches)
Example: TypeScript
const diff = await client.getVersionDiff(
  "admin/tdb-example-mydb/local/branch/main",
  "admin/tdb-example-mydb/local/branch/feature"
);
console.log(JSON.stringify(diff, null, 2));
// Shows field-level changes between branches

6. Merge a branch back to main

Apply all changes from your branch onto main. If both branches modified the same field, the merge fails with a conflict report — no silent overwrites.

before_commit is the common ancestor — the commit where feature branched off main. Find it with /api/log: the most recent commit ID that appears in both branches' history. If feature was just created and main has not moved since, main's head is the common ancestor.

before_commit and after_commit accept a branch name ("main"), a commit ID, or a data-version string — "commit:<id>" or "branch:<commit-id>", the same format as the TerminusDB-Data-Version response header.

Do not pass the target branch's own tip as before_commit. A branch tip is not a common ancestor — apply then computes a plain two-way diff between the two heads, and changes the target branch made since the divergence are silently reverted or overwritten with api:status: "api:success" and no conflict raised.

HTTP API

curl -u admin:root -X POST http://localhost:6363/api/apply/admin/tdb-example-mydb/local/branch/main \
  -H "Content-Type: application/json" \
  -d '{
    "before_commit": "commit:abc123def456",
    "after_commit": "feature",
    "commit_info": {
      "author": "[email protected]",
      "message": "Merge feature branch: add Widget product"
    }
  }'

Expected response (success):

Example: JSON
{"@type":"api:ApplyResponse","api:status":"api:success"}

If there are conflicts (HTTP 409):

Example: JSON
{
  "@type": "api:ApplyError",
  "api:status": "api:conflict",
  "api:witnesses": [
    {"@id": "Product/Widget", "price": {"@op": "Conflict", "@expected": 9.99, "@found": 12.99}}
  ]
}

@expected is the base value the patch expected to find; @found is the current value on the target branch. Conflicts must be resolved manually — TerminusDB never silently picks a winner.

Example: TypeScript
client.checkout("main"); // apply onto main
await client.apply(
  "commit:abc123def456", // common ancestor of main and feature
  "feature",             // source of changes
  "Merge feature branch"
);

7. Delete a branch

Remove a branch after merging. The commits remain in the database history but this branch name no longer reaches them.

HTTP API

curl -u admin:root -X DELETE http://localhost:6363/api/branch/admin/tdb-example-mydb/local/branch/feature

Expected response:

Example: JSON
{"@type":"api:BranchResponse","api:status":"api:success"}
Example: TypeScript
await client.deleteBranch("feature");

Complete workflow example

Here is the full branch workflow end-to-end — create, change, review, merge, clean up:

# 1. Create a feature branch
curl -u admin:root -X POST http://localhost:6363/api/branch/admin/tdb-example-mydb/local/branch/price-update \
  -H "Content-Type: application/json" \
  -d '{"origin": "admin/tdb-example-mydb/local/branch/main"}'

# 2. Make changes on the branch
curl -u admin:root -X POST \
  "http://localhost:6363/api/document/admin/tdb-example-mydb/local/branch/price-update?author=alice&message=Add+new+product" \
  -H "Content-Type: application/json" \
  -d '{"@id": "Product/Gadget", "@type": "Product", "name": "Gadget", "price": 24.99, "category": "electronics"}'

# 3. Review what changed (diff against main)
curl -u admin:root -X POST http://localhost:6363/api/diff/admin/tdb-example-mydb \
  -H "Content-Type: application/json" \
  -d '{"before_data_version": "main", "after_data_version": "price-update"}'

# 4. Merge into main (before_commit = common ancestor commit from /api/log)
curl -u admin:root -X POST http://localhost:6363/api/apply/admin/tdb-example-mydb/local/branch/main \
  -H "Content-Type: application/json" \
  -d '{"before_commit": "commit:abc123def456", "after_commit": "price-update", "commit_info": {"author": "alice", "message": "Merge price-update into main"}}'

# 5. Clean up the branch
curl -u admin:root -X DELETE http://localhost:6363/api/branch/admin/tdb-example-mydb/local/branch/price-update

Next steps

Was this helpful?