Skip to main content
LanceDB provides performant full-text search based on BM25, allowing you to incorporate keyword-based search in your retrieval solutions. This page shows examples on how to create and configure FTS indexes in LanceDB OSS and Enterprise, using the synchronous and asynchronous APIs.
In LanceDB Enterprise, create_fts_index API returns immediately, but index building happens asynchronously.

Creating FTS Indexes

Synchronous API

Use create_fts_index with synchronous LanceDB connections: Check FTS index status using the API: wait_for_index(...) waits until the named FTS index exists and index_stats(...) reports num_unindexed_rows == 0. It can time out if writes keep adding rows faster than the index catches up. If a table has multiple FTS indexes, specify the target text column when querying instead of relying on implicit selection.

Asynchronous API

When using async connections (connect_async), use create_index with the FTS configuration:
The create_fts_index method is not available on AsyncTable. Use create_index with FTS config instead.

Nested field paths

FTS indexes can target text leaves inside struct columns by passing a dotted path (for example, payload.text). The same path works for MatchQuery and PhraseQuery, and for the columns argument on async nearest_to_text queries. You can point an index at any string leaf nested in a struct, regardless of depth. The struct container itself isn’t indexable: you have to name a specific text field. LanceDB rejects paths that don’t resolve to a text leaf:
  • A struct container (for example, payload): raises ValueError: FTS index cannot be created ....
  • A non-text leaf such as an integer or float (for example, payload.count): raises the same error.
  • A path that doesn’t exist in the schema (for example, payload.missing): raises ValueError: Field path ... not found.
The async API accepts the same dotted paths through create_index:
Python

Configuration Options

FTS Parameters

  • max_token_length can filter out base64 blobs or long URLs.
  • Disabling with_position reduces index size but disables phrase queries.
  • ascii_folding helps with international text (e.g., “café” → “cafe”).

Tokenizer choices

base_tokenizer controls segmentation before token filters run:
  • simple, whitespace, and raw cover common tokenization strategies for space-delimited text.
  • ngram indexes overlapping character spans for substring-style matching.
  • icu uses bundled ICU4X word segmentation for mixed-language text and scripts where whitespace splitting is not enough. ICU stands for International Components for Unicode, and this tokenizer does not need external model files.
  • jieba/* is for Chinese word segmentation with Jieba.
  • lindera/* loads a compiled Lindera dictionary, such as lindera/ipadic for Japanese or lindera/ko-dic for Korean.
Model-backed tokenizers such as jieba/default, lindera/ipadic, and lindera/ko-dic require tokenizer model files in Lance’s language model home. Lance looks under the default platform data directory for lance/language_models, or you can set LANCE_LANGUAGE_MODEL_HOME to point to another model root. For example, jieba/default is resolved under <model-home>/jieba/default/.... language is used by token filters, not by the base tokenizer. Stemming supports Arabic, Danish, Dutch, English, Finnish, French, German, Greek, Hungarian, Italian, Norwegian, Portuguese, Romanian, Russian, Spanish, Swedish, Tamil, and Turkish. Built-in stop-word removal supports Danish, Dutch, English, Finnish, French, German, Hungarian, Italian, Norwegian, Portuguese, Russian, Spanish, and Swedish. For other stemming languages, set remove_stop_words=False or pass custom_stop_words.

Phrase Query Configuration

Enable phrase queries by setting:

Inspecting query tokenization

When a query returns unexpected FTS results, it helps to see the exact tokens the index will match. LanceDB exposes two ways to run the tokenizer directly:
  • Table.tokenize uses the tokenizer configured on an existing FTS index. This is the fastest way to reproduce what happens inside search(...) for a given column or named index.
  • The top-level tokenize helper runs the tokenizer without touching a table, so you can preview the behavior of a base_tokenizer, language, or ngram configuration before you build an index.
Both entry points return a list of tokens with the token text (after filters such as lowercasing, stemming, and stop-word removal) and the token position used by phrase matching.
Model-backed tokenizers such as jieba/* and lindera/* are rebuilt in the client from index metadata. For remote tables, the same tokenizer model files must also exist locally, in Lance’s language model home.

Tokenize against an existing FTS index

Specify exactly one of column or the index name. Passing a column requires that column to have exactly one FTS index; passing an index name uses that index regardless of column.

Tokenize without an index

Use this to explore tokenizer settings before you commit to a specific FTS configuration. The options mirror the FTS index parameters described above.

Indexing nested string fields

You can build an FTS index on a string field inside a struct by passing its full dotted path, like nested.text. The same path is used when you query the index through fts_columns, and the indexed column is reported back as the full path from list_indices().
Use the canonical Lance path: dot-separate each struct field from root to leaf (for example, metadata.author.name). The same convention applies to scalar and vector indexes.