--- title: "Schema evolution" sidebarTitle: "Schema Evolution" description: Learn how to manage table schemas in LanceDB, including adding, altering, and dropping columns. icon: "boxes-stacked" --- import { PySchemaAddSetup as SchemaAddSetup, 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, } from '/snippets/tables.mdx'; 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 ## Schema evolution operations {#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 Schema evolution operations are applied immediately but do not typically require rewriting all data. However, data type changes may involve more substantial operations. 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 {#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 {#set-up-the-example-table} First, let's create a sample table with product data to demonstrate schema evolution: {SchemaAddSetup} {TsSchemaAddSetup} {RsSchemaAddSetup} 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. ### Add columns with default values {#add-columns-with-default-values} Add boolean columns with default values for status tracking: {AddColumnsDefaultValues} {TsAddColumnsDefaultValues} {RsAddColumnsDefaultValues} ### Add nullable columns {#add-nullable-columns} Add timestamp columns that can contain NULL values: {AddColumnsNullable} {TsAddColumnsNullable} {RsAddColumnsNullable} When adding columns that should contain NULL values, be sure to cast the NULL to the appropriate type, e.g., `cast(NULL as timestamp)`. ## Alter existing columns {#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 {#set-up-the-example-table-2} Create a table with a custom schema to demonstrate column alterations: {SchemaAlterSetup} {TsSchemaAlterSetup} {RsSchemaAlterSetup} ### Rename columns {#rename-columns} Change column names to better reflect their purpose: {AlterColumnsRename} {TsAlterColumnsRename} {RsAlterColumnsRename} ### Change data types {#change-data-types} Convert column data types for better performance or compatibility: {AlterColumnsDataType} {TsAlterColumnsDataType} {RsAlterColumnsDataType} ### Make columns nullable {#make-columns-nullable} You can alter columns to contain NULL values: {AlterColumnsNullable} {TsAlterColumnsNullable} {RsAlterColumnsNullable} 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 {#multiple-changes-at-once} Apply several alterations in a single operation: {AlterColumnsMultiple} {TsAlterColumnsMultiple} {RsAlterColumnsMultiple} ### Expression-based type changes {#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: {AlterColumnsWithExpression} {TsAlterColumnsWithExpression} {RsAlterColumnsWithExpression} ### Alter embedding types and dimensions {#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. {AlterVectorColumn} {TsAlterVectorColumn} {RsAlterVectorColumn} **`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. 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. ## Update field metadata {#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. {SchemaFieldMetadataMerge} {TsSchemaFieldMetadataMerge} {RsSchemaFieldMetadataMerge} To overwrite a field's metadata entirely instead of merging, set `replace` to `true`: {SchemaFieldMetadataReplace} {TsSchemaFieldMetadataReplace} {RsSchemaFieldMetadataReplace} 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. ### Canonical metadata keys {#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:` | 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"` | These keys are conventions, not enforced constraints. LanceDB does not validate their values, but following the conventions keeps your tables consistent with Enterprise tooling. ## Drop columns {#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 {#set-up-the-example-table-3} Create a table with temporary columns that we'll remove: {SchemaDropSetup} {TsSchemaDropSetup} {RsSchemaDropSetup} ### Drop single columns {#drop-single-columns} Remove individual columns that are no longer needed: {DropColumnsSingle} {TsDropColumnsSingle} {RsDropColumnsSingle} ### Drop multiple columns {#drop-multiple-columns} Remove several columns at once for efficiency: {DropColumnsMultiple} {TsDropColumnsMultiple} {RsDropColumnsMultiple} Dropping columns cannot be undone. Make sure you have backups or are certain before removing columns.