> ## Documentation Index
> Fetch the complete documentation index at: https://lancedb-bcbb4faf-mintlify-dca53b3f.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Branches

> Fork isolated, writable lines of table history in LanceDB. Run experiments, backfills, and index rebuilds without disturbing production reads on main.

export const RsBranchIndex = "use lancedb::index::scalar::FtsIndexBuilder;\nuse lancedb::index::Index;\n\n// Build and validate indexes on a branch before using the configuration on\n// main.\nlet dev = products\n    .create_branch(\"index-dev\", Ref::Version(None, None))\n    .await\n    .unwrap();\n\n// A vector (ANN) index and a full-text search index, both branch-scoped.\ndev.create_index(&[\"vector\"], Index::Auto)\n    .execute()\n    .await\n    .unwrap();\ndev.create_index(&[\"text\"], Index::FTS(FtsIndexBuilder::default()))\n    .execute()\n    .await\n    .unwrap();\n\n// Both indexes live only on the branch; main still has none.\nprintln!(\"Branch indexes: {}\", dev.list_indices().await.unwrap().len()); // 2\nprintln!(\"Main indexes: {}\", products.list_indices().await.unwrap().len()); // 0\n";

export const TsBranchIndex = "// Build and validate indexes on a branch before using the configuration on\n// main.\nconst dev = await productBranches.create(\"index-dev\");\n\n// A vector (ANN) index and a full-text search index, both branch-scoped.\nawait dev.createIndex(\"vector\", {\n  config: lancedb.Index.ivfPq({\n    distanceType: \"cosine\",\n    numPartitions: 1,\n    numSubVectors: 2,\n  }),\n});\nawait dev.createIndex(\"text\", { config: lancedb.Index.fts() });\n\n// Both indexes live only on the branch; main still has none.\nconsole.log((await dev.listIndices()).map((ix) => ix.name)); // branch: two indexes\nconsole.log((await products.listIndices()).map((ix) => ix.name)); // main: [] (untouched)\n";

export const BranchIndex = "# Build and validate indexes on a branch before using the configuration on\n# main.\ndev = products.branches.create(\"index-dev\")\n\n# A vector (ANN) index and a full-text search index, both branch-scoped.\ndev.create_index(\n    \"vector\",\n    config=IvfPq(distance_type=\"cosine\", num_partitions=1, num_sub_vectors=2),\n)\ndev.create_index(\"text\", config=FTS())\n\n# Both indexes live only on the branch; main still has none.\nprint([ix.name for ix in dev.list_indices()])  # branch: two indexes\nprint([ix.name for ix in products.list_indices()])  # main: [] (untouched)\n";

export const RsBranchUpsertToMain = "// This is a row-level upsert, not a merge of branch histories.\n// `merge_insert` updates matching rows and inserts new rows using a stable\n// unique key. Filter the branch read if you only want to apply some results.\nlet schema = candidate.schema().await.unwrap();\nlet batches = candidate\n    .query()\n    .execute()\n    .await\n    .unwrap()\n    .try_collect::<Vec<_>>()\n    .await\n    .unwrap();\nlet rows_to_apply = RecordBatchIterator::new(batches.into_iter().map(Ok), schema);\n\nlet mut merge = branches_table.merge_insert(&[\"id\"]);\nmerge\n    .when_matched_update_all(None) // update rows that already exist on main\n    .when_not_matched_insert_all(); // insert rows that are new on the branch\nmerge.execute(Box::new(rows_to_apply)).await.unwrap();\n";

export const TsBranchUpsertToMain = "// This is a row-level upsert, not a merge of branch histories.\n// `mergeInsert` updates matching rows and inserts new rows using a stable\n// unique key. Filter the branch read if you only want to apply some results.\nconst rowsToApply = await candidate.toArrow();\nawait table\n  .mergeInsert(\"id\")\n  .whenMatchedUpdateAll() // update rows that already exist on main\n  .whenNotMatchedInsertAll() // insert rows that are new on the branch\n  .execute(rowsToApply);\n";

export const BranchUpsertToMain = "# This is a row-level upsert, not a merge of branch histories.\n# `merge_insert` updates matching rows and inserts new rows using a stable\n# unique key. Filter the branch read if you only want to apply some results.\nrows_to_apply = candidate.to_arrow()\n(\n    table.merge_insert(\"id\")\n    .when_matched_update_all()  # update rows that already exist on main\n    .when_not_matched_insert_all()  # insert rows that are new on the branch\n    .execute(rows_to_apply)\n)\n";

export const RsBranchDelete = "// Delete the branch and its branch-local history. Data on main is safe.\nbranches_table.delete_branch(\"exp\").await.unwrap();\n";

export const TsBranchDelete = "// Delete the branch and its branch-local history. Data on main is safe.\nawait branches.delete(\"exp\");\n";

export const BranchDelete = "# Delete the branch and its branch-local history. Data on main is safe.\ntable.branches.delete(\"exp\")\n";

export const RsBranchReopen = "// Reopen an existing branch by name from the table handle...\nlet checked_out = branches_table.checkout_branch(\"exp\", None).await.unwrap();\n// ...or open it directly via the connection's builder.\nlet opened = db\n    .open_table(\"quotes_branches_example\")\n    .branch(\"exp\")\n    .execute()\n    .await\n    .unwrap();\nprintln!(\n    \"Reopened rows: {}, {}\",\n    checked_out.count_rows(None).await.unwrap(),\n    opened.count_rows(None).await.unwrap()\n); // both 4\n";

export const TsBranchReopen = "// Reopen an existing branch by name from the table handle...\nconst checkedOut = await branches.checkout(\"exp\");\n// ...or open it directly from the database connection.\nconst branchHandle = await db.openTable(\n  \"quotes_branches_example\",\n  undefined,\n  { branch: \"exp\" },\n);\nconsole.log(await checkedOut.countRows(), await branchHandle.countRows()); // both 4\n";

export const BranchReopen = "# Reopen an existing branch by name from the table handle...\nchecked_out = table.branches.checkout(\"exp\")\n# ...or open it directly from the database connection.\nbranch_handle = db.open_table(\"quotes_branches_example\", branch=\"exp\")\nprint(checked_out.count_rows(), branch_handle.count_rows())  # both 4\n";

export const RsBranchWrite = "// Writes land on the branch handle only; main is left untouched.\nbranch\n    .add(make_quotes_reader(vec![(4, \"Lancelot\", \"For the realm!\")]))\n    .execute()\n    .await\n    .unwrap();\nprintln!(\"Branch rows: {}\", branch.count_rows(None).await.unwrap()); // 4\nprintln!(\"Main rows: {}\", branches_table.count_rows(None).await.unwrap()); // 3\n\n// List every branch, each mapped to its metadata (including its fork point).\nprintln!(\"Branches: {:?}\", branches_table.list_branches().await.unwrap());\n";

export const TsBranchWrite = "// Writes land on the branch handle only; main is left untouched.\nawait branch.add([{ id: 4, author: \"Lancelot\", quote: \"For the realm!\" }]);\nconsole.log(await branch.countRows()); // 4 rows on the branch\nconsole.log(await table.countRows()); // 3 rows; main is unaffected\n\n// List every branch, each mapped to its metadata (including its fork point).\nconsole.log(await branches.list());\n";

export const BranchWrite = "# Writes land on the branch handle only; main is left untouched.\nbranch.add([{\"id\": 4, \"author\": \"Lancelot\", \"quote\": \"For the realm!\"}])\nprint(branch.count_rows())  # 4 rows on the branch\nprint(table.count_rows())  # 3 rows; main is unaffected\n\n# List every branch, each mapped to its metadata (including its fork point).\nprint(table.branches.list())\n";

export const RsBranchCreate = "// Fork an isolated, writable branch from main's latest version.\n// `create_branch` returns a table handle scoped to the new branch.\nlet branch = branches_table\n    .create_branch(\"exp\", Ref::Version(None, None))\n    .await\n    .unwrap();\n";

export const TsBranchCreate = "// Fork an isolated, writable branch from main's latest version.\n// `create` returns a table handle scoped to the new branch.\nconst branch = await branches.create(\"exp\");\n";

export const BranchCreate = "# Fork an isolated, writable branch from main's latest version.\n# `create` returns a table handle scoped to the new branch.\nbranch = table.branches.create(\"exp\")\n";

export const RsConnectEnterpriseQuickstart = "let uri = \"db://your-database-uri\";\nlet api_key = \"your-api-key\";\nlet region = \"us-east-1\";\nlet host_override = \"https://your-enterprise-endpoint.com\";\n";

export const TsConnectEnterpriseQuickstart = "const uri = \"db://your-database-uri\";\nconst apiKey = \"your-api-key\";\nconst region = \"us-east-1\";\nconst hostOverride = \"https://your-enterprise-endpoint.com\";\n\nconst db = await lancedb.connect(uri, {\n  apiKey,\n  region,\n  hostOverride,\n});\n";

export const PyConnectEnterpriseQuickstart = "uri = \"db://your-database-uri\"\napi_key = \"your-api-key\"\nregion = \"us-east-1\"\nhost_override = \"https://your-enterprise-endpoint.com\"\n\ndb = lancedb.connect(\n    uri=uri,\n    api_key=api_key,\n    region=region,\n    host_override=host_override,\n)\n";

export const RsConnect = "async fn connect_example(uri: &str) {\n    let db = connect(uri).execute().await.unwrap();\n    let _ = db;\n}\n";

export const TsConnect = "import * as lancedb from \"@lancedb/lancedb\";\n\nasync function connectExample(uri: string) {\n  const db = await lancedb.connect(uri);\n  return db;\n}\n";

export const PyConnect = "import lancedb\n\nuri = \"ex_lancedb\"\ndb = lancedb.connect(uri)\n";

A branch is an isolated, writable line of history forked from `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](/tables/versioning),
and you can [tag](/tables/versioning#tag-based-versioning) a version or `checkout`
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](#branches-vs-tags-vs-checkout) at
the end of this page lays out when to reach for each.

<Note>
  Branches are supported on local and namespace-backed tables in LanceDB OSS, as
  well as on LanceDB Enterprise (remote) tables.
</Note>

## 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](/tables/create)), 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.

<CodeGroup>
  <CodeBlock filename="Python" language="Python" icon="python">
    {PyConnect}
  </CodeBlock>

  <CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
    {TsConnect}
  </CodeBlock>

  <CodeBlock filename="Rust" language="Rust" icon="rust">
    { "use lancedb::connect;\n\n" }

    {RsConnect}
  </CodeBlock>
</CodeGroup>

### LanceDB Enterprise

<Badge color="red">Enterprise</Badge>

Branching on LanceDB Enterprise works the same way as on OSS, but you connect to your
Enterprise deployment with a `db://` 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.

<CodeGroup>
  <CodeBlock filename="Python" language="Python" icon="python">
    {PyConnectEnterpriseQuickstart}
  </CodeBlock>

  <CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
    {TsConnectEnterpriseQuickstart}
  </CodeBlock>

  <CodeBlock filename="Rust" language="Rust" icon="rust">
    {RsConnectEnterpriseQuickstart}
  </CodeBlock>
</CodeGroup>

## 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
small `quotes` table with three rows on `main`.

### Create a branch

Forking from `main` 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.

<CodeGroup>
  <CodeBlock filename="Python" language="Python" icon="python">
    {BranchCreate}
  </CodeBlock>

  <CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
    {TsBranchCreate}
  </CodeBlock>

  <CodeBlock filename="Rust" language="Rust" icon="rust">
    {RsBranchCreate}
  </CodeBlock>
</CodeGroup>

### Write to a branch

Writes go through the branch handle and stay there — the `main` 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.

<CodeGroup>
  <CodeBlock filename="Python" language="Python" icon="python">
    {BranchWrite}
  </CodeBlock>

  <CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
    {TsBranchWrite}
  </CodeBlock>

  <CodeBlock filename="Rust" language="Rust" icon="rust">
    {RsBranchWrite}
  </CodeBlock>
</CodeGroup>

### 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.

<CodeGroup>
  <CodeBlock filename="Python" language="Python" icon="python">
    {BranchReopen}
  </CodeBlock>

  <CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
    {TsBranchReopen}
  </CodeBlock>

  <CodeBlock filename="Rust" language="Rust" icon="rust">
    {RsBranchReopen}
  </CodeBlock>
</CodeGroup>

### 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`](#apply-branch-tested-changes-to-main)
below.

<CodeGroup>
  <CodeBlock filename="Python" language="Python" icon="python">
    {BranchDelete}
  </CodeBlock>

  <CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
    {TsBranchDelete}
  </CodeBlock>

  <CodeBlock filename="Rust" language="Rust" icon="rust">
    {RsBranchDelete}
  </CodeBlock>
</CodeGroup>

## Apply branch-tested changes to `main`

<Warning>
  A branch has its own writable history. LanceDB does not currently reconcile one
  branch's history with another or detect conflicts between them. To carry
  accepted work forward, rerun the validated operation against `main` or
  explicitly write selected results to it.
</Warning>

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 `main` using the configuration validated
  on the branch.
* **Selected row results:** upsert those rows into `main` using 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`](/tables/update#merge-incoming-rows-by-key) 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:

<CodeGroup>
  <CodeBlock filename="Python" language="Python" icon="python">
    {BranchUpsertToMain}
  </CodeBlock>

  <CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
    {TsBranchUpsertToMain}
  </CodeBlock>

  <CodeBlock filename="Rust" language="Rust" icon="rust">
    {RsBranchUpsertToMain}
  </CodeBlock>
</CodeGroup>

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 the `main` 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 <Badge color="red">Enterprise</Badge>
deployments, where `main` is typically serving production traffic while you tune
an index configuration on the side.

<CodeGroup>
  <CodeBlock filename="Python" language="Python" icon="python">
    {BranchIndex}
  </CodeBlock>

  <CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
    {TsBranchIndex}
  </CodeBlock>

  <CodeBlock filename="Rust" language="Rust" icon="rust">
    {RsBranchIndex}
  </CodeBlock>
</CodeGroup>

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](#work-with-branches)**                                           | ✅ Yes     | Write on top of a point in history without touching `main`.                         |
| **[Tag](/tables/versioning#tag-based-versioning)**                          | ❌ No      | Attach a human-readable label to an existing version; protects it from cleanup.     |
| **[`checkout(version)`](/tables/versioning#rollback-to-previous-versions)** | ❌ No      | Read a historical version of `main` without forking. Read-only until you `restore`. |

<Note>
  For linear version history — creating versions, listing them, rolling back, and
  tagging — see the [Versioning and Reproducibility](/tables/versioning) guide.
</Note>
