# Full-text search with a document schema
## What it is
An index with a document schema stores typed JSON documents. You declare ranking fields: `string` fields with full-text search enabled (BM25 ranking), `dense_vector` fields (ANN similarity), and `sparse_vector` fields. Non-ranking fields ride along as metadata, auto-indexed for filtering at upsert time. One index can mix all three ranking types; each query picks its scoring signal.
## Steps
1. **Build the schema** declaring which string fields get BM25. Only declared fields are full-text indexed; undeclared text is metadata, not searchable by BM25.
2. **Create the index** with the schema (API version 2026-07; REST or Python SDK).
3. **Upsert documents** as JSON records matching the schema. Ranking fields must be present and correctly typed.
4. **Query with the right clause:** BM25 text clauses on the full-text fields, dense queries on the vector fields, or combine a text-match filter with a dense rank in one request.
5. **Remember what BM25 is not.** A full-text field does not call an embedding model. It is Lucene-style keyword ranking. For semantic matching you need the dense field too.
## Traps
1. Expecting semantic matches from a BM25-only index: synonyms and paraphrases will not match. That is working as designed.
2. Declaring every string field as full-text: index what you search, not everything. Unneeded full-text fields cost index size and write speed.
3. Mixing APIs: document-schema indexes use the newer API version; older SDK versions do not know the schema calls. Keep the SDK current.