Metadata filters

LlamaIndex · Practical · 10 min

Filters enforce hard constraints such as year, product or user permissions that similarity search alone can't.

Similarity search finds text that sounds related. It can't guarantee hard conditions like "only 2025 documents" or "only documents this user may see". Metadata filters add those conditions to the search.

filters = MetadataFilters(filters=[
    MetadataFilter(key="department", value="hr"),
    MetadataFilter(key="year", value=2024, operator=FilterOperator.GTE),
])
retriever = index.as_retriever(filters=filters, similarity_top_k=3)
  • MetadataFilter(key, value, operator): the default operator is equality. Others include NE, GT, GTE, LT, LTE, IN, NIN (not in) and TEXT_MATCH.
  • MetadataFilters(filters=[...], condition="and"): combine with "and" (default) or "or". Filter groups can be nested.

The same filters= argument works on as_query_engine and as_chat_engine.

Filters only work on metadata you stored, so plan it during ingestion: department, product, language, date as a number (such as 2025) and access level.

Support for each operator depends on the vector store. The simple in-memory store and most popular databases handle the common ones, but check the integration's docs before relying on TEXT_MATCH or nested groups.

For permissions, filters are the right tool: add the user's allowed groups as a filter on every query. Never rely on the prompt ("don't reveal HR documents"); a filter makes sure such nodes are never retrieved at all.

When the filter comes from the user's question ("reports from 2023"), an LLM can extract it, but validate the values before using them.

Example

from llama_index.core import Document, VectorStoreIndex
from llama_index.core.vector_stores import FilterOperator, MetadataFilter, MetadataFilters

index = VectorStoreIndex.from_documents([
    Document(text="2023 HR handbook: 20 leave days.", metadata={"dept": "hr", "year": 2023}),
    Document(text="2025 HR handbook: 25 leave days.", metadata={"dept": "hr", "year": 2025}),
    Document(text="2025 sales targets.", metadata={"dept": "sales", "year": 2025}),
])

filters = MetadataFilters(filters=[
    MetadataFilter(key="dept", value="hr"),                                # equality
    MetadataFilter(key="year", value=2024, operator=FilterOperator.GTE),
])
for r in index.as_retriever(filters=filters, similarity_top_k=5).retrieve("leave days"):
    print(r.node.metadata, r.node.text)
# {'dept': 'hr', 'year': 2025} 2025 HR handbook: 25 leave days.

qe = index.as_query_engine(filters=filters)    # same filters for answers

Remember

  • MetadataFilters([MetadataFilter(key, value, operator)]) restricts search
  • Works on retrievers, query engines and chat engines via filters=
  • Use filters, not prompts, for permissions

Watch out for

  • Storing years as strings like "2025" and then using GTE comparisons, which may compare text instead of numbers or fail.

Check yourself

  1. How should you stop users from retrieving documents they are not allowed to see?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: developers.llamaindex.ai/python/framework/module_guides/indexing/vector_store_guide