main (or from any
other branch). Anything you do on a branch (e.g., adding rows, changing the schema,
building an index) stays on that branch, so main keeps serving production
reads exactly as before. Branches are a natural fit when you want to:
- Experiment with a new index, schema change, or reprocessing step without
affecting live queries on
main. - Run a backfill or migration you’d like to validate before applying it to
main. - Hand a collaborator a frozen point-in-time fork while you keep writing to
main.
How branches relate to versions and tags
Every LanceDB table already tracks a linear history of versions, and you can tag a version orcheckout
one to read it. Branches add the missing piece: a separate, writable line of
history. Where a tag is a read-only label and checkout is a read-only view, a
branch forks from a point in history and then evolves on its own. Creating or
checking out a branch hands you a new table handle whose reads and writes are
scoped to that branch. The comparison table at
the end of this page lays out when to reach for each.
Branches are supported on local and namespace-backed tables in LanceDB OSS, as
well as on LanceDB Enterprise (remote) tables.
Connect to a table
The branch API is identical no matter how you connect — only the connection itself differs between OSS and Enterprise. Establish a connection (db) and open
a table (see Create a table), then use the same branch calls
in every example that follows.
LanceDB OSS
Point LanceDB at a local directory (or an object-storage URI) to use it as an embedded library.LanceDB Enterprise
Enterprise Branching on LanceDB Enterprise works the same way as on OSS, but you connect to your Enterprise deployment with adb:// URI, an API key, and your
region. Once you have a connection, open a table and use the same branch calls in
every example that follows.
Work with branches
The lifecycle of a branch is short and predictable: fork it, write to it, reopen it whenever you need it, and delete it once you’re done. The examples below use a smallquotes table with three rows on main.
Create a branch
Forking frommain returns a table handle scoped to the new branch. main is
the reserved default source, so create needs only a name; to fork from
somewhere else, pass a branch name, a specific version, or both.
Write to a branch
Writes go through the branch handle and stay there — themain handle keeps
reporting its original row count. Listing branches returns a mapping of each
branch name to its metadata, including the version it was forked from.
Reopen a branch
A branch outlives the handle that created it. Reopen it later by name — either from an existing table handle or straight from the connection when you open the table. Both routes give you a writable handle tracking the branch’s latest state.Delete a branch
Deleting a branch removes it and its branch-local history;main is untouched.
Before deleting a branch, make sure you’ve retained any results you need — see
Apply branch-tested changes to main
below.
Apply branch-tested changes to main
How you apply a validated change depends on the type of work:
- Backfill or transformation: rerun the validated job against
main. - Schema change: apply the same reviewed schema operation to
main. - Index change: build the index on
mainusing the configuration validated on the branch. - Selected row results: upsert those rows into
mainusing a stable unique key.
Upsert selected branch rows into main
If the result you want to retain is a set of inserted or updated rows, use
merge_insert to write them to
main. Despite its name, merge_insert is a row-ingestion operation: it
matches incoming rows by key and does not merge branch histories.
Read the rows you want from the reviewed branch, then upsert them into main on
your key column — id in this example. Here, candidate is the branch handle:
This operation transfers only the inserted or updated rows in its input. It does
not transfer branch history, schema changes, indexes, or branch-local deletions,
and it does not determine which rows changed after the branch was created.
Reapplying an identical payload is idempotent on the key, but the operation is
not conflict-aware. If main has diverged, incoming branch rows can overwrite
newer values with the same key. Read and upsert the whole branch only when that
overwrite is intentional; otherwise, filter the branch read to the rows you
intend to apply.
Build indexes on a branch
One of the most useful things a branch buys you is a safe place to build and validate an index without affecting what’s in production on themain branch.
Fork a branch, create your vector (ANN) and full-text search (FTS)
indexes on it, and check recall and latency. Once you have selected a
configuration, build the corresponding index on main through your normal
deployment workflow. Because the indexes live on the branch, queries against
main never see a half-built index and are never slowed down by the branch’s
build.
This pattern is especially valuable on Enterprise
deployments, where main is typically serving production traffic while you tune
an index configuration on the side.
Schema changes such as adding, altering, or dropping columns are branch-scoped in
the same way, so you can stage and review a larger reshaping of a table before
applying the same schema operations to main.
Branches vs. tags vs. versions
Now that you’re familiar with branches, you can see how they complement the other ways LanceDB give you to work with table history. Choose these approaches based on whether you need to label, read, or write data at a point in history:| Feature | Writable? | Purpose |
|---|---|---|
| Branch | ✅ Yes | Write on top of a point in history without touching main. |
| Tag | ❌ No | Attach a human-readable label to an existing version; protects it from cleanup. |
checkout(version) | ❌ No | Read a historical version of main without forking. Read-only until you restore. |
For linear version history — creating versions, listing them, rolling back, and
tagging — see the Versioning and Reproducibility guide.