Files
lancedb/docs/web/management/table.mdx
T
Brendan Clement 34bfd03f4c docs: keep availability summaries readable in the comparison
Four summaries pointed at the table or sections below, which reads wrongly as a row of the comparison. They now stand alone, and insert links to its bulk-ingest section.
2026-10-08 21:38:07 -07:00

1165 lines
43 KiB
Plaintext

---
title: "Tables"
sidebarTitle: "Table"
description: "The object most operations act on: creating, opening, renaming, cloning and dropping one."
icon: table
keywords: ["table", "create_table", "open_table", "clone", "rename", "drop"]
availability:
oss: available
enterprise: available
summary: >-
Tables are created, evolved, versioned and dropped in the embedded library
and on a deployment. Renaming, cloning, dropping every table and pruning old
versions differ between the two.
---
import {
PySchemaAddSetup,
TsSchemaAddSetup as TsSchemaAddSetup,
RsSchemaAddSetup as RsSchemaAddSetup,
PyAddColumnsDefaultValues as AddColumnsDefaultValues,
TsAddColumnsDefaultValues as TsAddColumnsDefaultValues,
RsAddColumnsDefaultValues as RsAddColumnsDefaultValues,
PyAddColumnsNullable as AddColumnsNullable,
TsAddColumnsNullable as TsAddColumnsNullable,
RsAddColumnsNullable as RsAddColumnsNullable,
PySchemaAlterSetup as SchemaAlterSetup,
TsSchemaAlterSetup as TsSchemaAlterSetup,
RsSchemaAlterSetup as RsSchemaAlterSetup,
PyAlterColumnsRename as AlterColumnsRename,
TsAlterColumnsRename as TsAlterColumnsRename,
RsAlterColumnsRename as RsAlterColumnsRename,
PyAlterColumnsDataType as AlterColumnsDataType,
TsAlterColumnsDataType as TsAlterColumnsDataType,
RsAlterColumnsDataType as RsAlterColumnsDataType,
PyAlterColumnsNullable as AlterColumnsNullable,
TsAlterColumnsNullable as TsAlterColumnsNullable,
RsAlterColumnsNullable as RsAlterColumnsNullable,
PyAlterColumnsMultiple as AlterColumnsMultiple,
TsAlterColumnsMultiple as TsAlterColumnsMultiple,
PyAlterColumnsWithExpression as AlterColumnsWithExpression,
TsAlterColumnsWithExpression as TsAlterColumnsWithExpression,
RsAlterColumnsMultiple as RsAlterColumnsMultiple,
RsAlterColumnsWithExpression as RsAlterColumnsWithExpression,
PySchemaDropSetup as SchemaDropSetup,
TsSchemaDropSetup as TsSchemaDropSetup,
RsSchemaDropSetup as RsSchemaDropSetup,
PyDropColumnsSingle as DropColumnsSingle,
TsDropColumnsSingle as TsDropColumnsSingle,
RsDropColumnsSingle as RsDropColumnsSingle,
PyDropColumnsMultiple as DropColumnsMultiple,
TsDropColumnsMultiple as TsDropColumnsMultiple,
RsDropColumnsMultiple as RsDropColumnsMultiple,
PyAlterVectorColumn as AlterVectorColumn,
TsAlterVectorColumn as TsAlterVectorColumn,
RsAlterVectorColumn as RsAlterVectorColumn,
PySchemaFieldMetadataMerge as SchemaFieldMetadataMerge,
TsSchemaFieldMetadataMerge as TsSchemaFieldMetadataMerge,
RsSchemaFieldMetadataMerge as RsSchemaFieldMetadataMerge,
PySchemaFieldMetadataReplace as SchemaFieldMetadataReplace,
TsSchemaFieldMetadataReplace as TsSchemaFieldMetadataReplace,
RsSchemaFieldMetadataReplace as RsSchemaFieldMetadataReplace,
PyVersioningBasicSetup as VersioningBasicSetup,
TsVersioningBasicSetup as TsVersioningBasicSetup,
RsVersioningBasicSetup as RsVersioningBasicSetup,
PyVersioningCheckInitialVersion as VersioningCheckInitialVersion,
TsVersioningCheckInitialVersion as TsVersioningCheckInitialVersion,
RsVersioningCheckInitialVersion as RsVersioningCheckInitialVersion,
PyVersioningUpdateData as VersioningUpdateData,
TsVersioningUpdateData as TsVersioningUpdateData,
RsVersioningUpdateData as RsVersioningUpdateData,
PyVersioningAddData as VersioningAddData,
TsVersioningAddData as TsVersioningAddData,
RsVersioningAddData as RsVersioningAddData,
PyVersioningCheckVersionsAfterMod as VersioningCheckVersionsAfterMod,
TsVersioningCheckVersionsAfterMod as TsVersioningCheckVersionsAfterMod,
RsVersioningCheckVersionsAfterMod as RsVersioningCheckVersionsAfterMod,
PyVersioningListAllVersions as VersioningListAllVersions,
TsVersioningListAllVersions as TsVersioningListAllVersions,
RsVersioningListAllVersions as RsVersioningListAllVersions,
PyVersioningRollback as VersioningRollback,
TsVersioningRollback as TsVersioningRollback,
RsVersioningRollback as RsVersioningRollback,
PyVersioningCheckoutLatest as VersioningCheckoutLatest,
TsVersioningCheckoutLatest as TsVersioningCheckoutLatest,
RsVersioningCheckoutLatest as RsVersioningCheckoutLatest,
PyVersioningDeleteData as VersioningDeleteData,
TsVersioningDeleteData as TsVersioningDeleteData,
RsVersioningDeleteData as RsVersioningDeleteData,
PyVersioningTags as VersioningTags,
TsVersioningTags as TsVersioningTags,
RsVersioningTags as RsVersioningTags,
RsVersioningMakeQuotesReader as RsVersioningMakeQuotesReader,
} from '/snippets/tables.mdx';
import {
PyMultimodalImports as MultimodalImports,
TsMultimodalImports as TsMultimodalImports,
RsMultimodalImports as RsMultimodalImports,
PyCreateDummyData as CreateDummyData,
TsCreateDummyData as TsCreateDummyData,
RsCreateDummyData as RsCreateDummyData,
PyDefineSchema as DefineSchema,
TsDefineSchema as TsDefineSchema,
RsDefineSchema as RsDefineSchema,
PyIngestData as IngestData,
TsIngestData as TsIngestData,
RsIngestData as RsIngestData,
PySearchData as SearchData,
TsSearchData as TsSearchData,
RsSearchData as RsSearchData,
PyProcessResults as ProcessResults,
TsProcessResults as TsProcessResults,
RsProcessResults as RsProcessResults,
PyBlobApiSchema as BlobApiSchema,
TsBlobApiSchema as TsBlobApiSchema,
RsBlobApiSchema as RsBlobApiSchema,
PyBlobApiIngest as BlobApiIngest,
TsBlobApiIngest as TsBlobApiIngest,
RsBlobApiIngest as RsBlobApiIngest,
PyBlobApiToPandas as BlobApiToPandas,
PyQueryToPandasKwargs as QueryToPandasKwargs,
} from '/snippets/multimodal.mdx';
A table is rows and columns, versioned. It is what searches read, what writes
commit to, and what indexes and branches belong to.
This page covers the table as a catalog object. Getting data into one is
[Ingest](/ingestion); changing its shape is
[Schema evolution](/management/table#schema).
<Note>
On a deployment, tables are also created and dropped in SQL with [`CREATE TABLE` and `DROP TABLE`](/sql/ddl/table).
</Note>
## What a table carries {#carries}
| | |
| --- | --- |
| **Columns** | including vectors, blobs, and [computed](/feature-engineering/add-computed-column) columns |
| **Versions** | every write commits one, so reads can [travel back](/management/table#versioning) |
| **Indexes** | vector, full-text and scalar, built over its columns |
| **Branches** | isolated lines of change you can [merge or discard](/tables/branching) |
## Creating {#create}
<CodeGroup>
```python Python icon="python"
tbl = db.create_table("documents", data)
tbl = db.create_table("documents", schema=schema) # empty, typed
```
```typescript TypeScript icon="square-js"
const tbl = await db.createTable("documents", data);
const tbl = await db.createEmptyTable("documents", schema);
```
```sql SQL
CREATE TABLE documents (id BIGINT, title VARCHAR)
```
</CodeGroup>
`data` can be a list of objects, an Arrow table or reader, a pandas or polars
frame, or Pydantic models. [Ingesting data](/ingestion) covers each, plus
`exist_ok` and `mode="overwrite"` for re-runnable scripts.
<Warning>
An explicit schema is where nullability is decided. In the embedded library,
creation is also the only point at which a table can be given **stable row
ids**; on a deployment, whether a new table gets them is the deployment's
setting. See [Limits](/basics/limits#schema).
</Warning>
## Opening and listing {#open}
<CodeGroup>
```python Python icon="python"
tbl = db.open_table("documents")
db.table_names()
```
```typescript TypeScript icon="square-js"
const tbl = await db.openTable("documents");
await db.tableNames();
```
</CodeGroup>
Each accepts `namespace_path` to act inside a [namespace](/management/namespace)
rather than at the database root. Python's `table_exists` works only on a
namespace connection, where it takes the table's full path as a list; local
directory and deployment connections raise `NotImplementedError`. TypeScript
has no `tableExists`.
## Renaming and cloning {#rename-clone}
```python
db.rename_table("documents", "docs")
db.clone_table(
"docs_snapshot",
source_uri="s3://bucket/db/documents.lance",
source_version=42, # or source_tag="release-1"
is_shallow=True,
)
```
Renaming is not supported on a local directory connection. On a deployment,
whether a table can be renamed depends on how the deployment stores its
catalog.
A **shallow** clone -- the default -- references the source's data files rather
than copying them, so it is fast and cheap regardless of table size. It is the
practical way to branch an experiment off a known version, or to pin a training
set to the exact version a model saw.
<Warning>
A shallow clone depends on the source's files continuing to exist. If the source
is [compacted](/maintenance/compaction) past the retention window, the version
the clone points at can be pruned. Local OSS connections support shallow clones
only; `is_shallow=False` is not implemented. If the copy must outlive the
source's history, copy its data into an independent table instead. A deployment
also makes shallow clones only.
</Warning>
<Note>
**Enterprise job type:** `drop_table`. On a deployment that drops tables in the
background, fences the table's directory with a marker and deletes its content
as a job -- see
[Background jobs](/maintenance/background-jobs#teardown) for the namespace,
view, and function equivalent.
</Note>
## Dropping {#drop}
<CodeGroup>
```python Python icon="python"
db.drop_table("documents")
job = db.drop_table_async("documents")
db.drop_all_tables()
```
```typescript TypeScript icon="square-js"
await db.dropTable("documents");
const job = await db.dropTableAsync("documents");
await db.dropAllTables();
```
```sql SQL
DROP TABLE documents
```
</CodeGroup>
<Warning>
Dropping removes the table and **every version of it**.
[Versioning](/management/table#versioning) does not survive a drop, and neither
`drop_all_tables` nor `drop_database` asks for confirmation. Both work only on
a local database; on a deployment connection the SDK raises instead.
</Warning>
On a large table the drop can take a while, so `drop_table_async` returns a
[job](/basics/jobs) rather than blocking when the deployment drops tables in the
background. In the embedded library, and on a deployment that drops inline, the
returned job has already finished.
## The full surface {#surface}
| | Python | TypeScript | SQL |
| --- | --- | --- | --- |
| create | `create_table` | `createTable` | `CREATE TABLE` |
| create empty | `create_table(schema=)` | `createEmptyTable` | `CREATE TABLE` |
| open | `open_table` | `openTable` | -- |
| list | `table_names`, `list_tables` | `tableNames`, `listTables` | -- |
| exists | `table_exists` | -- | -- |
| rename | `rename_table` | `renameTable` | -- |
| clone | `clone_table` | `cloneTable` | -- |
| drop | `drop_table`, `drop_table_async` | `dropTable`, `dropTableAsync` | `DROP TABLE` |
| drop all | `drop_all_tables` | `dropAllTables` | -- |
## Data types {#data-types}
A table's schema is an **Arrow schema** -- there is no separate LanceDB type
system mapped onto storage, so a column is the same object to the query engine
as it is to the format.
| Category | Types |
| --- | --- |
| Numeric | `int8`-`int64` and unsigned, `float16`, `float32`, `float64` |
| String and binary | `utf8`, `large_utf8`, `binary`, `large_binary`, `fixed_size_binary(n)` |
| Date and time | `date32`, `date64`, `time32`, `time64`, `timestamp`, `duration` |
| Nested | `struct`, `list`, `large_list`, `fixed_size_list`, `map` |
| Lance extensions | vectors, [JSON](/sql/data-types#json), [blobs](#multimodal), bfloat16 |
A vector column is a `fixed_size_list<float32, dim>` -- an ordinary Arrow type,
which is why it is selectable and joinable but why arithmetic over it is list
arithmetic rather than scalar.
See [SQL data types](/sql/data-types) for the full reference, including the
extension types and pseudo-columns.
## Multimodal data {#multimodal}
Binary data -- images, audio, video, model artifacts -- lives in the table
beside the structured columns and embeddings that describe it, and is governed
by the same versioning and search.
Small values go in an ordinary `binary` column. Large ones go in a **blob**
column, stored out of line so a scan over the other columns stays fast. See
[Data types](/sql/data-types#blobs) for how the two differ to a reader.
### Store binary data {#multimodal-store-binary-data}
To store binary data, define a binary Arrow field in your schema (`pa.binary()` in Python, `Binary` in TypeScript, and `DataType::Binary` in Rust).
#### 1\. Setup and imports {#multimodal-1-setup-and-imports}
First, import the necessary libraries for LanceDB and Arrow in your SDK.
<CodeGroup>
<CodeBlock filename="Python" language="Python" icon="python">
{MultimodalImports}
</CodeBlock>
<CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
{TsMultimodalImports}
</CodeBlock>
<CodeBlock filename="Rust" language="Rust" icon="rust">
{RsMultimodalImports}
</CodeBlock>
</CodeGroup>
#### 2\. Prepare data {#multimodal-2-prepare-data}
For this example, we'll create some dummy in-memory images. In a real application, you would read these from files or an API. The key is to convert your data (image, audio, etc.) into a raw `bytes` object.
<CodeGroup>
<CodeBlock filename="Python" language="Python" icon="python">
{CreateDummyData}
</CodeBlock>
<CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
{TsCreateDummyData}
</CodeBlock>
<CodeBlock filename="Rust" language="Rust" icon="rust">
{RsCreateDummyData}
</CodeBlock>
</CodeGroup>
#### 3\. Define the schema {#multimodal-3-define-the-schema}
When creating the table, it is **highly recommended** to define the schema explicitly. This ensures that your binary data is correctly interpreted as a `binary` type by Arrow/LanceDB and not as a generic string or list.
<CodeGroup>
<CodeBlock filename="Python" language="Python" icon="python">
{DefineSchema}
</CodeBlock>
<CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
{TsDefineSchema}
</CodeBlock>
<CodeBlock filename="Rust" language="Rust" icon="rust">
{RsDefineSchema}
</CodeBlock>
</CodeGroup>
#### 4\. Ingest data {#multimodal-4-ingest-data}
Now, create the table using the data and the defined schema.
<CodeGroup>
<CodeBlock filename="Python" language="Python" icon="python">
{IngestData}
</CodeBlock>
<CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
{TsIngestData}
</CodeBlock>
<CodeBlock filename="Rust" language="Rust" icon="rust">
{RsIngestData}
</CodeBlock>
</CodeGroup>
### Retrieve and use blobs {#multimodal-retrieve-and-use-blobs}
When you search your LanceDB table, you can retrieve the binary column just like any other metadata.
<CodeGroup>
<CodeBlock filename="Python" language="Python" icon="python">
{SearchData}
</CodeBlock>
<CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
{TsSearchData}
</CodeBlock>
<CodeBlock filename="Rust" language="Rust" icon="rust">
{RsSearchData}
</CodeBlock>
</CodeGroup>
#### Convert bytes back to objects {#multimodal-convert-bytes-back-to-objects}
Once you have the bytes back from the search result, you can decode them into the original format (for example, an image object or audio buffer).
<CodeGroup>
<CodeBlock filename="Python" language="Python" icon="python">
{ProcessResults}
</CodeBlock>
<CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
{TsProcessResults}
</CodeBlock>
<CodeBlock filename="Rust" language="Rust" icon="rust">
{RsProcessResults}
</CodeBlock>
</CodeGroup>
### Large Blobs (Blob API) {#multimodal-large-blobs-blob-api}
For larger files like high-resolution images or videos, Lance provides a specialized **Blob API**. By using a large-binary Arrow type (`pa.large_binary()` in Python, `LargeBinary` in TypeScript, and `DataType::LargeBinary` in Rust) and specific metadata, you enable **lazy loading** and optimized encoding. This allows you to work with massive datasets without loading all binary data into memory upfront.
#### 1\. Define a blob schema {#multimodal-1-define-a-blob-schema}
To use the Blob API, you must mark the column with `{"lance-encoding:blob": "true"}` metadata.
<CodeGroup>
<CodeBlock filename="Python" language="Python" icon="python">
{BlobApiSchema}
</CodeBlock>
<CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
{TsBlobApiSchema}
</CodeBlock>
<CodeBlock filename="Rust" language="Rust" icon="rust">
{RsBlobApiSchema}
</CodeBlock>
</CodeGroup>
#### 2\. Ingest large blobs {#multimodal-2-ingest-large-blobs}
You can then ingest data normally, and Lance will handle the optimized storage.
<CodeGroup>
<CodeBlock filename="Python" language="Python" icon="python">
{BlobApiIngest}
</CodeBlock>
<CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
{TsBlobApiIngest}
</CodeBlock>
<CodeBlock filename="Rust" language="Rust" icon="rust">
{RsBlobApiIngest}
</CodeBlock>
</CodeGroup>
<Card>
For more advanced usage, including random access and file-like reading of blobs, see the
Lance format's [blob API documentation](https://lance.org/guide/blob/).
</Card>
#### 3\. Convert blob tables to pandas {#multimodal-3-convert-blob-tables-to-pandas}
When you call `to_pandas()` on a local LanceDB table that contains Blob API columns, the `blob_mode` argument controls how those columns materialize. This is available in the Python SDK on local tables; remote tables raise `NotImplementedError`.
`blob_mode` accepts:
- `"lazy"` (default): returns blob columns as lazy `BlobFile` objects without eagerly materializing their payloads. Use this when you want to stream blob bytes on demand or only inspect a subset of rows. Namespace-backed local tables also use the Lance native blob-aware pandas conversion for lazy blobs; in-memory datasets fall back to the standard PyArrow `to_pandas()` path.
- `"bytes"`: eagerly materializes each blob as `bytes`. Use this when you need the raw payload in the DataFrame, for example to decode an image or audio clip in-process.
- `"descriptions"`: returns blob descriptors (offsets, sizes, and positions) instead of the data itself. Use this when you want to plan I/O without paying the cost of loading every blob.
`"bytes"` and `"descriptions"` require a filesystem-backed Lance dataset and are not supported on in-memory tables.
Extra keyword arguments are forwarded to the underlying PyArrow / Lance pandas conversion, so you can also pass options like `split_blocks` or `self_destruct`:
<CodeGroup>
<CodeBlock filename="Python" language="Python" icon="python">
{BlobApiToPandas}
</CodeBlock>
</CodeGroup>
Query builders also accept `blob_mode` on their `to_pandas()` method:
- Plain scans support `"lazy"`, `"bytes"`, and `"descriptions"` with filters, projections, aliases, `limit`, and `offset`.
- Vector, FTS, hybrid, and ordered queries can't materialize blob columns through `to_pandas()`; omit blob columns from the projection for those query shapes.
- This works on both sync and async query builders. Extra PyArrow kwargs like `split_blocks` and `self_destruct` are still forwarded.
<CodeGroup>
<CodeBlock filename="Python" language="Python" icon="python">
{QueryToPandasKwargs}
</CodeBlock>
</CodeGroup>
### Other modalities {#multimodal-other-modalities}
The `pa.binary()` and `pa.large_binary()` types are universal. You can use this same pattern for other types of multimodal data:
- **Audio:** Read `.wav` or `.mp3` files as bytes.
- **Video:** Store video transitions or full clips using the Blob API.
- **PDFs/Documents:** Store the raw file content for document search.
### bfloat16 {#multimodal-bfloat16}
Sixteen-bit floats in the bfloat16 layout, stored as `fixed_size_binary(2)`.
Useful as a vector element type when halving the memory matters more than the
precision does.
## Schema evolution {#schema}
Schema evolution enables non-breaking modifications to a database table's structure — such as adding columns, altering data types, or dropping fields — to adapt to evolving data requirements without service interruptions.
LanceDB supports ACID-compliant schema evolution through granular operations (add/alter/drop columns), allowing you to:
* Iterate Safely: Modify schemas in production with versioned datasets and backward compatibility
* Scale Seamlessly: Handle ML model iterations, regulatory changes, or feature additions
* Optimize Continuously: Remove unused fields or enforce new constraints without downtime
<Note>
In SQL, schema changes are [`ALTER TABLE`](/sql/ddl/table#alter).
</Note>
### Schema evolution operations {#schema-schema-evolution-operations}
LanceDB supports four primary schema evolution operations:
1. **Adding new columns**: Extend your table with additional attributes
2. **Altering existing columns**: Change column names, data types, or nullability
3. **Updating field metadata**: Attach or change per-column Arrow metadata
4. **Dropping columns**: Remove unnecessary columns from your schema
<Tip title="Schema Evolution Performance">
Schema evolution operations are applied immediately but do not typically require rewriting all data. However, data type changes may involve more substantial operations.
</Tip>
Each schema evolution operation commits a new table version and returns status metadata such as
the committed `version`. Run these operations from a mutable table handle; if you checked out an
older version for reads, call `checkout_latest` / `checkoutLatest` before modifying the schema.
### Add new columns {#schema-add-new-columns}
You can add new columns to a table with the [`add_columns`](https://lancedb.github.io/lancedb/python/python/#lancedb.table.Table.add_columns)
method in Python, [`addColumns`](https://lancedb.github.io/lancedb/js/classes/Table/#addcolumns) in TypeScript/JavaScript, or `add_columns` in Rust.
New columns are populated based on SQL expressions you provide.
#### Set up the example table {#schema-set-up-the-example-table}
First, let's create a sample table with product data to demonstrate schema evolution:
<CodeGroup>
<CodeBlock filename="Python" language="Python" icon="python">
{PySchemaAddSetup}
</CodeBlock>
<CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
{TsSchemaAddSetup}
</CodeBlock>
<CodeBlock filename="Rust" language="Rust" icon="rust">
{RsSchemaAddSetup}
</CodeBlock>
</CodeGroup>
<Note>
Columns derived from existing data -- SQL expressions over other columns -- are
covered in [Data evolution](/feature-engineering/data-evolution), because
deriving values is a feature-engineering job rather than a schema one.
</Note>
#### Add columns with default values {#schema-add-columns-with-default-values}
Add boolean columns with default values for status tracking:
<CodeGroup>
<CodeBlock filename="Python" language="Python" icon="python">
{AddColumnsDefaultValues}
</CodeBlock>
<CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
{TsAddColumnsDefaultValues}
</CodeBlock>
<CodeBlock filename="Rust" language="Rust" icon="rust">
{RsAddColumnsDefaultValues}
</CodeBlock>
</CodeGroup>
#### Add nullable columns {#schema-add-nullable-columns}
Add timestamp columns that can contain NULL values:
<CodeGroup>
<CodeBlock filename="Python" language="Python" icon="python">
{AddColumnsNullable}
</CodeBlock>
<CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
{TsAddColumnsNullable}
</CodeBlock>
<CodeBlock filename="Rust" language="Rust" icon="rust">
{RsAddColumnsNullable}
</CodeBlock>
</CodeGroup>
<Warning title="NULL Values in New Columns">
When adding columns that should contain NULL values, be sure to cast the NULL to the appropriate type, e.g., `cast(NULL as timestamp)`.
</Warning>
### Alter existing columns {#schema-alter-existing-columns}
You can alter columns using the [`alter_columns`](https://lancedb.github.io/lancedb/python/python/#lancedb.table.Table.alter_columns)
method in Python, [`alterColumns`](https://lancedb.github.io/lancedb/js/classes/Table/#altercolumns) in TypeScript/JavaScript, or `alter_columns` in Rust. This allows you to:
- Rename a column
- Change a column's data type
- Modify nullability (whether a column can contain NULL values)
#### Set up the example table {#schema-set-up-the-example-table-2}
Create a table with a custom schema to demonstrate column alterations:
<CodeGroup>
<CodeBlock filename="Python" language="Python" icon="python">
{SchemaAlterSetup}
</CodeBlock>
<CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
{TsSchemaAlterSetup}
</CodeBlock>
<CodeBlock filename="Rust" language="Rust" icon="rust">
{RsSchemaAlterSetup}
</CodeBlock>
</CodeGroup>
#### Rename columns {#schema-rename-columns}
Change column names to better reflect their purpose:
<CodeGroup>
<CodeBlock filename="Python" language="Python" icon="python">
{AlterColumnsRename}
</CodeBlock>
<CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
{TsAlterColumnsRename}
</CodeBlock>
<CodeBlock filename="Rust" language="Rust" icon="rust">
{RsAlterColumnsRename}
</CodeBlock>
</CodeGroup>
#### Change data types {#schema-change-data-types}
Convert column data types for better performance or compatibility:
<CodeGroup>
<CodeBlock filename="Python" language="Python" icon="python">
{AlterColumnsDataType}
</CodeBlock>
<CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
{TsAlterColumnsDataType}
</CodeBlock>
<CodeBlock filename="Rust" language="Rust" icon="rust">
{RsAlterColumnsDataType}
</CodeBlock>
</CodeGroup>
#### Make columns nullable {#schema-make-columns-nullable}
You can alter columns to contain NULL values:
<CodeGroup>
<CodeBlock filename="Python" language="Python" icon="python">
{AlterColumnsNullable}
</CodeBlock>
<CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
{TsAlterColumnsNullable}
</CodeBlock>
<CodeBlock filename="Rust" language="Rust" icon="rust">
{RsAlterColumnsNullable}
</CodeBlock>
</CodeGroup>
Changing a column to nullable affects future writes and merges too: missing values are accepted
only when the target column is nullable.
#### Multiple changes at once {#schema-multiple-changes-at-once}
Apply several alterations in a single operation:
<CodeGroup>
<CodeBlock filename="Python" language="Python" icon="python">
{AlterColumnsMultiple}
</CodeBlock>
<CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
{TsAlterColumnsMultiple}
</CodeBlock>
<CodeBlock filename="Rust" language="Rust" icon="rust">
{RsAlterColumnsMultiple}
</CodeBlock>
</CodeGroup>
#### Expression-based type changes {#schema-expression-based-type-changes}
For transformations that are not simple casts (for example, converting `"$100"` to an integer), use a SQL-expression column add, then drop and rename:
<CodeGroup>
<CodeBlock filename="Python" language="Python" icon="python">
{AlterColumnsWithExpression}
</CodeBlock>
<CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
{TsAlterColumnsWithExpression}
</CodeBlock>
<CodeBlock filename="Rust" language="Rust" icon="rust">
{RsAlterColumnsWithExpression}
</CodeBlock>
</CodeGroup>
#### Alter embedding types and dimensions {#schema-alter-embedding-types-and-dimensions}
It's quite common to need to change an embedding column's schema, in case a new model becomes available with a different embedding dimension.
- In Python, the example shows an in-place type update when the cast is compatible.
- In TypeScript and Rust, the example shows a dimension change (`384 -> 1024`), which cannot be cast in-place.
For dimension changes, use this 3-step pattern: add a new column with the target type, drop the old column, then rename the new column to the original name.
<CodeGroup>
<CodeBlock filename="Python" language="Python" icon="python">
{AlterVectorColumn}
</CodeBlock>
<CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
{TsAlterVectorColumn}
</CodeBlock>
<CodeBlock filename="Rust" language="Rust" icon="rust">
{RsAlterVectorColumn}
</CodeBlock>
</CodeGroup>
<Warning>
**`FixedSizeList` Dimension Changes in TypeScript and Rust**
`alterColumns` / `alter_columns` can cast between compatible types, but changing `FixedSizeList` dimensions (for example `384 -> 1024`) is not a compatible cast.
For such cases, use `addColumns` / `add_columns` (with `arrow_cast`), then `dropColumns` / `drop_columns`, then rename the replacement column.
</Warning>
<Warning title="Data Type Changes">
Changing data types requires rewriting the column data and may be resource-intensive for large tables. Renaming columns or changing nullability is more efficient as it only updates metadata.
</Warning>
### Update field metadata {#schema-update-field-metadata}
Each column in a LanceDB table can carry a small key/value map of Arrow field metadata — useful
for annotating columns with units, provenance, PII flags, embedding model versions, or any other
schema-level context your application needs.
Use [`update_field_metadata`](https://lancedb.github.io/lancedb/python/python/#lancedb.table.Table.update_field_metadata)
in Python, [`updateFieldMetadata`](https://lancedb.github.io/lancedb/js/classes/Table/#updatefieldmetadata)
in TypeScript/JavaScript, or `update_field_metadata` in Rust to add, change, or remove these
key/value pairs without rewriting the column data. Each call commits a new table version and returns
the new `version`.
Each update targets one field by **dot-path**: top-level columns are addressed by name (for
example `"embedding"`), and nested fields by their full path (for example `"address.zip"`). By
default, the keys you pass are **merged** into the field's existing metadata — keys you do not
mention are preserved, and passing `None` (Python) or `null` (TypeScript) deletes a key. Set
`replace: true` to swap the field's entire metadata map instead of merging.
<CodeGroup>
<CodeBlock filename="Python" language="Python" icon="python">
{SchemaFieldMetadataMerge}
</CodeBlock>
<CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
{TsSchemaFieldMetadataMerge}
</CodeBlock>
<CodeBlock filename="Rust" language="Rust" icon="rust">
{RsSchemaFieldMetadataMerge}
</CodeBlock>
</CodeGroup>
To overwrite a field's metadata entirely instead of merging, set `replace` to `true`:
<CodeGroup>
<CodeBlock filename="Python" language="Python" icon="python">
{SchemaFieldMetadataReplace}
</CodeBlock>
<CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
{TsSchemaFieldMetadataReplace}
</CodeBlock>
<CodeBlock filename="Rust" language="Rust" icon="rust">
{RsSchemaFieldMetadataReplace}
</CodeBlock>
</CodeGroup>
<Tip>
You can pass multiple updates in a single call to change metadata on several fields at once —
each call commits a single new table version.
</Tip>
#### Canonical metadata keys {#schema-canonical-metadata-keys}
You can use any string as a metadata key. By convention, LanceDB treats keys prefixed with
`lancedb:` as canonical. LanceDB Enterprise displays these keys in the UI, and agents can rely
on finding this information in a consistent place. Use them when they fit your use case:
| Key | Purpose | Example |
| --- | --- | --- |
| `lancedb:description` | Human-readable description of the field. | `"CLIP embedding of the product image"` |
| `lancedb:tag:<name>` | User-defined key/value tag, where the suffix names the tag category. | `"lancedb:tag:model": "clip"` |
| `lancedb:logical-column` | Groups related columns into one logical column. For example, `feature_v1` and `feature_v2` might belong to the same logical column. | `"lancedb:logical-column": "feature"` |
| `lancedb:status` | Life cycle state of the column: `production`, `candidate`, `deprecated`, or `archived`. | `"lancedb:status": "production"` |
<Note>
These keys are conventions, not enforced constraints. LanceDB does not validate their values,
but following the conventions keeps your tables consistent with Enterprise tooling.
</Note>
### Drop columns {#schema-drop-columns}
You can remove columns using the [`drop_columns`](https://lancedb.github.io/lancedb/python/python/#lancedb.table.Table.drop_columns)
method in Python, [`dropColumns`](https://lancedb.github.io/lancedb/js/classes/Table/#dropcolumns) in TypeScript/JavaScript, or `drop_columns` in Rust.
#### Set Up the example table {#schema-set-up-the-example-table-3}
Create a table with temporary columns that we'll remove:
<CodeGroup>
<CodeBlock filename="Python" language="Python" icon="python">
{SchemaDropSetup}
</CodeBlock>
<CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
{TsSchemaDropSetup}
</CodeBlock>
<CodeBlock filename="Rust" language="Rust" icon="rust">
{RsSchemaDropSetup}
</CodeBlock>
</CodeGroup>
#### Drop single columns {#schema-drop-single-columns}
Remove individual columns that are no longer needed:
<CodeGroup>
<CodeBlock filename="Python" language="Python" icon="python">
{DropColumnsSingle}
</CodeBlock>
<CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
{TsDropColumnsSingle}
</CodeBlock>
<CodeBlock filename="Rust" language="Rust" icon="rust">
{RsDropColumnsSingle}
</CodeBlock>
</CodeGroup>
#### Drop multiple columns {#schema-drop-multiple-columns}
Remove several columns at once for efficiency:
<CodeGroup>
<CodeBlock filename="Python" language="Python" icon="python">
{DropColumnsMultiple}
</CodeBlock>
<CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
{TsDropColumnsMultiple}
</CodeBlock>
<CodeBlock filename="Rust" language="Rust" icon="rust">
{RsDropColumnsMultiple}
</CodeBlock>
</CodeGroup>
<Warning title="Irreversible Column Deletion">
Dropping columns cannot be undone. Make sure you have backups or are certain before removing columns.
</Warning>
## Versioning {#versioning}
This page shows the core table-versioning APIs used in the code snippets for Python, TypeScript, and Rust.
Each operation below maps directly to methods shown in the examples.
### Basic Versioning Example {#versioning-basic-versioning-example}
Let's create a table with sample data to demonstrate LanceDB's versioning capabilities:
#### Set Up the Table {#versioning-set-up-the-table}
First, let's create a table with some sample data:
<CodeGroup>
<CodeBlock filename="Python" language="Python" icon="python">
{VersioningBasicSetup}
</CodeBlock>
<CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
{TsVersioningBasicSetup}
</CodeBlock>
<CodeBlock filename="Rust" language="Rust" icon="rust">
{RsVersioningBasicSetup}
</CodeBlock>
</CodeGroup>
<Expandable title="Rust helper used in this page">
<CodeGroup>
<CodeBlock filename="Rust" language="Rust" icon="rust">
{RsVersioningMakeQuotesReader}
</CodeBlock>
</CodeGroup>
</Expandable>
#### Check Initial Version {#versioning-check-initial-version}
After creating the table, let's check the initial version information:
<CodeGroup>
<CodeBlock filename="Python" language="Python" icon="python">
{VersioningCheckInitialVersion}
</CodeBlock>
<CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
{TsVersioningCheckInitialVersion}
</CodeBlock>
<CodeBlock filename="Rust" language="Rust" icon="rust">
{RsVersioningCheckInitialVersion}
</CodeBlock>
</CodeGroup>
### Modify Data {#versioning-modify-data}
When you modify data through operations like update or delete, LanceDB automatically creates new versions.
#### Update Existing Data {#versioning-update-existing-data}
Let's update some existing records to see versioning in action:
<CodeGroup>
<CodeBlock filename="Python" language="Python" icon="python">
{VersioningUpdateData}
</CodeBlock>
<CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
{TsVersioningUpdateData}
</CodeBlock>
<CodeBlock filename="Rust" language="Rust" icon="rust">
{RsVersioningUpdateData}
</CodeBlock>
</CodeGroup>
#### Add New Data {#versioning-add-new-data}
Now let's add more records to the table:
<CodeGroup>
<CodeBlock filename="Python" language="Python" icon="python">
{VersioningAddData}
</CodeBlock>
<CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
{TsVersioningAddData}
</CodeBlock>
<CodeBlock filename="Rust" language="Rust" icon="rust">
{RsVersioningAddData}
</CodeBlock>
</CodeGroup>
#### Check Version Changes {#versioning-check-version-changes}
Let's see how the versions have changed after our modifications:
<CodeGroup>
<CodeBlock filename="Python" language="Python" icon="python">
{VersioningCheckVersionsAfterMod}
</CodeBlock>
<CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
{TsVersioningCheckVersionsAfterMod}
</CodeBlock>
<CodeBlock filename="Rust" language="Rust" icon="rust">
{RsVersioningCheckVersionsAfterMod}
</CodeBlock>
</CodeGroup>
### Rollback to Previous Versions {#versioning-rollback-to-previous-versions}
LanceDB supports fast rollbacks to any previous version without data duplication.
#### View All Versions {#versioning-view-all-versions}
First, let's see all the versions we've created:
<CodeGroup>
<CodeBlock filename="Python" language="Python" icon="python">
{VersioningListAllVersions}
</CodeBlock>
<CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
{TsVersioningListAllVersions}
</CodeBlock>
<CodeBlock filename="Rust" language="Rust" icon="rust">
{RsVersioningListAllVersions}
</CodeBlock>
</CodeGroup>
#### Restore a Version Snapshot {#versioning-restore-a-version-snapshot}
Now let's restore a captured version snapshot:
<CodeGroup>
<CodeBlock filename="Python" language="Python" icon="python">
{VersioningRollback}
</CodeBlock>
<CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
{TsVersioningRollback}
</CodeBlock>
<CodeBlock filename="Rust" language="Rust" icon="rust">
{RsVersioningRollback}
</CodeBlock>
</CodeGroup>
### Tag-Based Versioning {#versioning-tag-based-versioning}
Numeric table versions like `v3` or `v17` are precise but hard to remember. Tags
let you attach human-readable labels (e.g., `"prod"`, `"baseline"`,
`"q3-evaluation"`) to specific versions and check those out by name. They are
conceptually similar to git tags, and unlike numeric versions, **tagged versions
are preserved when old versions are pruned** (see the cleanup note at the bottom
of this page).
The tags API supports the standard CRUD operations — create, list, update, delete —
plus checking out by tag name.
<CodeGroup>
<CodeBlock filename="Python" language="Python" icon="python">
{VersioningTags}
</CodeBlock>
<CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
{TsVersioningTags}
</CodeBlock>
<CodeBlock filename="Rust" language="Rust" icon="rust">
{RsVersioningTags}
</CodeBlock>
</CodeGroup>
<Note>
Deleting a tag only removes the label, not the version it points to. After
deletion, the underlying table version becomes eligible for cleanup again.
</Note>
### Branches {#versioning-branches}
Beyond linear history, LanceDB also supports **branches** — isolated, writable
lines of history forked from `main` (or a specific version). Whereas tags and
`checkout` give you read-only views of existing versions, a branch has its own
writable history, making it ideal for experiments, backfills, and migrations
that you want to keep separate from production reads on `main`.
Branches are covered in their own guide: see [Branches](/tables/branching).
### Delete Data From the Table {#versioning-delete-data-from-the-table}
Let's demonstrate how deletions also create new versions:
#### Go Back to Latest Version {#versioning-go-back-to-latest-version}
First, let's return to the latest version:
<CodeGroup>
<CodeBlock filename="Python" language="Python" icon="python">
{VersioningCheckoutLatest}
</CodeBlock>
<CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
{TsVersioningCheckoutLatest}
</CodeBlock>
<CodeBlock filename="Rust" language="Rust" icon="rust">
{RsVersioningCheckoutLatest}
</CodeBlock>
</CodeGroup>
#### Delete Data {#versioning-delete-data}
Now let's delete some data to see how it affects versioning:
<CodeGroup>
<CodeBlock filename="Python" language="Python" icon="python">
{VersioningDeleteData}
</CodeBlock>
<CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
{TsVersioningDeleteData}
</CodeBlock>
<CodeBlock filename="Rust" language="Rust" icon="rust">
{RsVersioningDeleteData}
</CodeBlock>
</CodeGroup>
#### Version History and Operations {#versioning-version-history-and-operations}
On a fresh table, the snippets in this guide produce this version sequence:
1. `v1`: create table (`create_table` / `createTable` / `create_table`)
2. `v2`: update rows (`update`)
3. `v3`: add rows (`add`)
4. `v4`: restore snapshot (`restore`) from `version_after_mod`/`versionAfterMod`
5. `v5`: delete rows (`delete`)
Read-only and checkout operations shown here (`list_versions`/`listVersions`, `version`, `checkout`, `checkout_latest`/`checkoutLatest`) do not create new versions.
The version metadata fields can differ by backend. Direct table-backed version listing exposes a
timestamp, while namespace-backed listing may expose fields such as `manifest_path`,
`manifest_size`, `e_tag`, and `timestamp_millis`. In deployments that use managed versioning,
prefer the table version APIs exposed by LanceDB Enterprise or the namespace service instead of
mixing in lower-level Lance file operations.
<Note>
**System Operations**
System operations like `optimize()`, index updates, and table compaction also increment table version numbers.
In LanceDB OSS, `optimize()` can prune older versions based on its retention setting (`cleanup_older_than`, 7 days by default),
which is when old-version files are removed and disk space is reclaimed. A deployment prunes them in the background
when its configuration enables [cleanup](/maintenance/cleanup); `optimize()` is not available there.
**Tagged versions are exempt from cleanup.** A version with a tag pointing at it is
retained regardless of age, and its files are not removed by `optimize()`. To make
a tagged version eligible for pruning, [delete the tag](#versioning-tag-based-versioning) first.
</Note>