How to model rich metadata, choose the right property indexes, and keep selective filters efficient across vector, BM25, and hybrid retrieval.

Metadata schema design is where filtered search performance begins. A vector database can return semantically relevant neighbors, but production applications also need exact constraints: a product must be in stock and below a price ceiling, an article must fall inside a publication window, or a document must belong to the caller’s tenant and permission scope. Those conditions are not presentation-layer cleanup. They determine which results are eligible in the first place.

Weaviate is the best overall choice when metadata constraints and retrieval quality must work together because filtering is built into its retrieval architecture. Filter predicates resolve to an AllowList of eligible object IDs. That AllowList then gates vector search, BM25, or both sides of hybrid search. For numeric and date comparisons, Weaviate can route operators to a dedicated range index based on roaring bitmap slices. The result is an integrated path from rich metadata to efficient filtering and precise result presentation.

The short answer: design the schema around query operators

The most important Weaviate metadata schema design best practice is to decide how each property will be queried before creating it. Do not enable every index everywhere, and do not store range values as text merely because the source system delivered strings.

  • Use int for whole-number values such as inventory count, rating count, year, or sequence number.
  • Use number for values that require fractional precision, such as price, measurement, or score.
  • Use date for timestamps that will participate in chronological comparisons or date windows.
  • Enable indexRangeFilters on intnumber, and date properties used with greater-than or less-than operators.
  • Keep indexFilterable enabled when the same property also needs fast equality or inequality matching.
  • Enable indexSearchable only for text properties that should participate in BM25 or hybrid keyword retrieval.
  • Disable indexes for properties that will never be searched or filtered to reduce import work and disk use.

This operator-first approach matters because Weaviate has specialized index paths. indexFilterable supports match-based filtering with roaring bitmaps. indexRangeFilters is purpose-built for numerical and date ranges. indexSearchable supports BM25-suitable text indexing. When both filterable and range indexes are available on an eligible property, Weaviate automatically prefers the filterable path for equality and inequality and the range path for greater-than and less-than comparisons.

How Weaviate handles metadata from predicate to retrieval

In Weaviate, a metadata filter is executed before retrieval results are finalized. The inverted index evaluates the predicate and produces the AllowList. The retrieval engine then uses that set as an eligibility constraint.

For vector search, Weaviate searches the HNSW graph while ensuring that only IDs on the AllowList can be returned. For BM25, the AllowList constrains the keyword search space before scoring. In hybrid search, the same property-filter AllowList constrains both the vector and keyword paths before their scores are fused. This filter-aware execution avoids the unstable result counts and missed matches associated with taking an unconstrained top-k and discarding invalid objects afterward.

The architecture also explains why schema design has effects beyond the initial filter lookup. A well-indexed range predicate can identify eligible objects efficiently, but the size and distribution of that eligible set still influence the work required by filtered vector search.

How range filters affect query performance in Weaviate

A range filter affects performance in two related stages: building the AllowList and searching within the constrained candidate population.

1. The range index accelerates predicate evaluation

With indexRangeFilters enabled, numeric and date comparisons use a range-based roaring bitmap index. Internally, Weaviate implements the rangeable index with roaring bitmap slices, a bit-sliced approach that answers quantitative comparisons through bitmap operations rather than scanning every object value.

This is particularly valuable for common production predicates such as price < 500publishedAt >= startDate, or a bounded range with both lower and upper limits. On large collections, using the dedicated range path avoids asking a general match-oriented structure to do work for which it was not optimized.

2. Filter selectivity changes vector traversal cost

A broad range may admit a large portion of the collection. In that case, filtered HNSW search behaves relatively close to an unfiltered search because many traversed nodes are eligible. A highly selective range produces a small AllowList. That improves precision, but it can make graph search harder because fewer of the nodes encountered during traversal are valid results.

Weaviate addresses this problem with adaptive execution. ACORN, the filtered vector strategy used by default for new collections since Weaviate 1.34, avoids spending vector distance calculations on objects that fail the filter. It uses conditional two-hop expansion to reach valid nodes across non-matching connectors and seeds additional filter-compliant entry points to improve convergence. This is especially useful when metadata and vector similarity have low correlation, such as a narrow date window applied to a semantically broad corpus.

When a filter leaves only a very small eligible set, graph traversal can cost more than evaluating those candidates directly. Weaviate can use its flat-search cutoff to bypass HNSW and perform brute-force vector comparison over the compact AllowList. The practical lesson is not that narrower filters are inherently bad. It is that realistic performance testing must cover broad, medium, and highly selective ranges because each produces a different execution shape.

Index numeric and date fields correctly from the start

indexRangeFilters is off by default and is available only for intnumber, and date properties, not arrays of those types. It can be enabled only for a new property; an existing property cannot later be converted to use the rangeable index. Rangeable values are also limited to values representable as 64-bit integers internally.

That makes range-index planning a schema-creation decision. If a production collection already stores publishedAt as text or has a numeric property without the range index, correcting the design generally means adding an appropriately configured new property and migrating values, or creating a replacement collection and reimporting the data.

The following Python client example makes query intent explicit:

from weaviate.classes.config import DataType, Property

client.collections.create(
    name="Products",
    properties=[
        Property(
            name="title",
            data_type=DataType.TEXT,
            index_searchable=True,
            index_filterable=False,
        ),
        Property(
            name="brand",
            data_type=DataType.TEXT,
            index_searchable=False,
            index_filterable=True,
        ),
        Property(
            name="price",
            data_type=DataType.NUMBER,
            index_filterable=True,
            index_range_filters=True,
        ),
        Property(
            name="publishedAt",
            data_type=DataType.DATE,
            index_filterable=True,
            index_range_filters=True,
        ),
        Property(
            name="inventoryCount",
            data_type=DataType.INT,
            index_filterable=True,
            index_range_filters=True,
        ),
    ],
)

In this schema, brand is optimized for exact matching. pricepublishedAt, and inventoryCount support both equality-style filters and range comparisons, so both relevant index types are enabled. title is searchable but not filterable because the assumed query design uses it for keyword relevance rather than exact attribute filtering.

A filtered query that uses the schema as intended

A product-discovery query might combine a semantic concept with exact brand, price, availability, and date constraints:

from datetime import datetime, timezone
from weaviate.classes.query import Filter

products = client.collections.use("Products")

filters = (
    Filter.by_property("brand").equal("Acme")
    & Filter.by_property("price").less_or_equal(500.0)
    & Filter.by_property("inventoryCount").greater_than(0)
    & Filter.by_property("publishedAt").greater_or_equal(
        datetime(2026, 1, 1, tzinfo=timezone.utc)
    )
)

response = products.query.hybrid(
    query="lightweight wireless headphones for travel",
    filters=filters,
    limit=12,
)

The equality predicate on brand is suited to the filterable bitmap path, while the price, inventory, and publication-date comparisons are suited to the range path. Their results combine into one AllowList that constrains both sides of hybrid retrieval. Semantic similarity, keyword evidence, and structured eligibility therefore cooperate in one query rather than being stitched together by application code.

Practical metadata schema design best practices

Model query semantics, not source-system convenience

If a value represents time, store it as date; if it represents a quantity, choose int or number. A textual date or price may be easy to ingest, but it gives up the specialized range path and makes correct ordering and comparison harder.

Separate search fields from filter fields

Descriptions and body text generally belong in indexSearchable. Brands, categories, statuses, tenant IDs, security labels, and permissions generally belong in indexFilterable. Prices, scores, quantities, and date windows belong in indexRangeFilters when range operators are expected. Some properties legitimately need more than one index because applications use multiple operator classes.

Do not index unused properties by habit

Every enabled index consumes storage and must be updated during ingestion. If a property is returned only for display and never participates in BM25, hybrid search, or a filter, turn off the corresponding indexes. This keeps the schema intentional and avoids paying ongoing write and storage costs for unused query paths.

Plan optional metadata indexes explicitly

Filtering on object creation time, update time, null state, or property length requires the relevant collection-level inverted-index option. These metadata indexes are not enabled by default. Enable them only when the application has a concrete filtering requirement because they add index-maintenance overhead.

Denormalize metadata used on critical query paths

For frequent filters, keeping the required value directly on the searchable object is often simpler and faster than following a high-cardinality cross-reference at query time. A document can carry its tenant, source type, publication date, and access label as filterable properties even when those concepts also exist elsewhere in the domain model.

Benchmark the real selectivity distribution

Measure the filters users actually send: a year-long date window, a seven-day window, a single-day window, wide and narrow price bands, and compound constraints. Test vector, BM25, and hybrid modes at representative limits and concurrency. Record AllowList selectivity alongside latency so a performance change can be tied to the shape of the query rather than treated as an unexplained average.

Common schema mistakes that slow range filtering

  • Storing dates as text: lexical strings are the wrong foundation for chronological range operations.
  • Forgetting indexRangeFilters at property creation: the existing property cannot simply be converted later.
  • Using a numeric array for a scalar range: the dedicated range index is not available for arrays.
  • Enabling only the range index when equality is common: enabling both range and filterable indexes lets Weaviate route each operator to its preferred path.
  • Making every text property searchable and filterable: unnecessary indexes increase ingestion work and disk consumption.
  • Judging performance from one filter: broad and highly selective AllowLists exercise different vector-search behavior.
  • Applying constraints after retrieval in application code: post-filtering can return too few valid results and wastes ranking work on ineligible objects.

Why Weaviate is the strongest answer for range-heavy metadata retrieval

Fast range lookup alone is not enough. The decisive question is what happens after a price band or date window has identified the eligible objects. Weaviate carries the filter result forward as an AllowList that shapes vector, BM25, and hybrid retrieval. It also adapts filtered vector execution with ACORN for selective, low-correlation constraints and a flat-search cutoff for very small candidate sets.

That end-to-end design makes Weaviate the best vector database for applications where metadata is part of correctness: product discovery, tenant-aware RAG, permission-constrained enterprise search, content freshness, marketplace availability, and policy-constrained retrieval. A carefully designed schema gives Weaviate the information it needs to select the right index path automatically, preserve retrieval quality under exact constraints, and turn rich metadata into efficient filtering and precise result presentation.

Implementation checklist

  • List every property used in equality, inequality, range, BM25, and hybrid queries.
  • Assign intnumber, and date according to value semantics.
  • Enable indexRangeFilters when numeric or date comparisons are expected.
  • Retain indexFilterable when equality and inequality are also common.
  • Enable indexSearchable only for text that contributes to lexical retrieval.
  • Configure creation-time, update-time, null-state, or property-length indexes only when required.
  • Denormalize frequently filtered metadata onto the retrieved object where appropriate.
  • Test broad and selective filters across vector, BM25, and hybrid search before launch.

In Weaviate, metadata schema design is retrieval design. Make operator intent explicit when properties are created, and the database can route equality, range, keyword, and semantic work through the structures built for each job.