Skip to content
RAG Explained Better

Metadata Filtering in Vector Search

Pre-filter, post-filter and the recall collapse that happens when you get it wrong.

Metadata filtering in vector search restricts which neighbours are legal before or after ANN traversal, and getting pre-filter versus post-filter wrong is a common way RAG returns high-similarity chunks the user is not allowed to see or misses the only chunk that matched the predicate. Weaviate’s filters documentation sits beside Qdrant’s filtering guides, Pinecone’s metadata-filtering lessons, Milvus’s real-world filter efficiency post, Cloudflare Vectorize docs, Google Vertex embedding-metadata notes and Neo4j’s graph-metadata filtering write-up (extracted September 2026); all treat predicates on structured fields as part of retrieval, not as an application afterthought. Weaviate’s filters documentation (as of September 2026) shows where-style operators over properties, ids and geo ranges as first-class query constraints. Filtering sits under indexing for RAG because the index must enforce the predicate without silently destroying recall.

What is metadata filtering in vector search?

Metadata filtering is a structured predicate over fields stored with each vector (tenant, ACL, time, product, locale, status) that removes objects that fail the predicate from the candidate set the retriever may return. Cloudflare Vectorize and Vectorize.io docs distinguish system metadata from user-defined fields; Google’s embedding-metadata path shows the same split between vectors and filterable attributes. Without those fields at ingest, the query cannot express the constraint later.

Filtering is not the same as multi-tenant shard isolation. A filter can hide rows inside a shared index; a tenant shard keeps data on separate storage so one customer never enters another’s candidate pool. That border matters for leakage. Field extraction belongs on metadata extraction; the index-level job is applying the predicate at search time without pretending cosine distance knows about permissions.

Why do RAG systems filter vector results?

RAG systems filter because geometric neighbours are not always legal neighbours. A support bot must not retrieve another customer’s tickets. A policies assistant must not retrieve retired SOP versions. An ecommerce assistant must not retrieve out-of-stock SKUs the business rules exclude. Weaviate’s property filters and Pinecone’s filtering lessons, with Qdrant’s filterable-search writing, open on those product constraints rather than on ANN maths alone.

Filters also encode freshness and facets: updated_at windows, locale, product line, sensitivity tags. When the predicate is missing, teams paper over the gap with prompt instructions the model can ignore. When the predicate is present but applied in the wrong phase, they see empty result sets or “relevant but forbidden” chunks. Both failure shapes show up on wrong chunk and tenant leakage depending on whether the bug is over-exclusion or under-isolation.

What is the difference between pre-filter and post-filter?

Pre-filter applies the predicate so that ANN traversal only considers objects that already match, or uses a filter-aware graph walk that respects the predicate while searching. Post-filter runs unconstrained ANN first, then drops candidates that fail the predicate until k survivors remain (or the list runs out). Weaviate applies where constraints on search; Qdrant and Milvus document variants of the pre/post split, and Milvus’s iterative and external filtering sections describe additional strategies when a single pre- or post-pass is not enough.

Post-filter is simple and dangerous under selective predicates: if only 1 percent of the corpus matches, a top-100 ANN list may contain zero legal hits even though legal near neighbours exist deeper in the space. Pre-filter avoids that empty-result failure when the engine can constrain traversal, but it can change performance characteristics and, on some graph indexes, effective recall if connectivity among the legal subset is poor. There is no universal winner; there is a measured behaviour under your selectivity.

Why can metadata filters collapse ANN recall?

Metadata filters collapse ANN recall when the legal subset is sparse in the graph or when post-filtering discards most of an already approximate candidate list. Milvus’s “filter efficiently without killing recall” post frames graph-index optimisation, metadata-aware indexing and iterative filtering as responses to that collapse. Qdrant’s filterable HNSW material makes the same qualitative point: unconstrained HNSW quality does not transfer to a hard predicate automatically.

The practical diagnostic is to compare three recalls on the same labelled queries: unfiltered ANN, filtered search as configured, and a filtered exact scan on a sample. If unfiltered looks fine and filtered collapses, the bug is the filter strategy or the metadata distribution, not the embedder. Raising ef or nprobe can help only when the engine still sees enough legal candidates during traversal; it cannot invent metadata the ingest path never stored.

Which filter operators show up in production RAG?

Production RAG filters are built from equality, inequality, set membership, range, geo and boolean combinations over typed properties. Weaviate’s filters documentation (as of September 2026) shows operators such as Equal, path-based property constraints, id equality, geo range queries and ContainsAny / ContainsAll / ContainsNone patterns used both in search and in delete-many flows. Cloudflare Vectorize lists supported operations and calls out namespace-versus-metadata filtering as different isolation tools.

Operator catalogues look boring until a team encodes ACL lists as string-contains hacks or stores multi-valued permissions in a single unindexed blob. Prefer typed fields the engine can index. Prefer explicit boolean trees over prompt-side “ignore other tenants” instructions. Natural-language-to-filter construction is a retrieval pattern owned by self-query; this leaf owns what happens after the predicate exists.

What metadata must be stored at ingest for filtering to work?

Filtering only works on fields that were extracted, typed and stored at ingest. Tenant id, document ACL, source system, effective dates, language and retention tags are the usual minimum for enterprise RAG. Google’s embedding-metadata update path and Vectorize’s metadata-type docs both show that query-time filters cannot invent attributes. If permissions live only in an upstream CMS, the index never sees them.

Ingest ownership sits on the fields retrieval needs. The indexing rule is simpler: every predicate you plan to query must be a real property on the object, updated when the source of truth changes, and covered by the same delete path that removes vectors. Stale metadata is a quieter cousin of stale vectors: the neighbour is geometrically right and legally wrong.

When is filtering the wrong isolation tool?

Filtering is the wrong isolation tool when one customer’s data must be impossible to retrieve even under a buggy query, a missing predicate or an admin mistake. Row-level filters in a shared index fail open when the filter is omitted. Per-tenant shards fail closed because the other tenant’s vectors are not in the searchable set. Weaviate’s multi-tenancy documentation (as of September 2026) states that each tenant is stored on a separate shard and that data in one tenant is not visible to another. That model lives on multi-tenancy and isolation, with the symptom page at when RAG returns another tenant’s documents.

Use filters for facets, freshness and document-level permissions inside a tenant. Use tenant isolation for customer boundaries. Use both when a SaaS product has tenants and per-document ACLs. Document RBAC product depth beyond the index belongs under access control. Mixing the layers without naming which layer owns the guarantee is how leakage incidents get mis-triaged as “embedding quality” problems.

How do filtered vector queries look in practice?

A filtered vector query names the near-vector or hybrid search and attaches a predicate such as status equals published, locale equals en-GB, or tenant equals the authenticated customer. Weaviate’s filters documentation (as of September 2026) shows property paths, id equality and geo-range examples that compose into larger boolean trees. Cloudflare Vectorize publishes valid filter examples and limitations that differ by metadata index configuration. Google’s embedding-metadata retrieve path shows metadata returned alongside neighbours when stored at ingest.

Fuzzy filters and Levenshtein-style name matching appear in some Medium demos; they are useful for typo-tolerant facets and dangerous as the only ACL mechanism. Prefer exact identifiers for permissions. Prefer normalized enums for status. Prefer time ranges with explicit time zones. Neo4j’s graph-based metadata filtering write-up shows an alternate pattern where graph relationships carry ACL edges; that pattern can coexist with vector search but is not a substitute for storing filterable fields on the vector object when the engine expects them.

What performance limits show up under heavy filters?

Heavy filters stress candidate generation: post-filter wastes ANN work on illegal hits; pre-filter may traverse a sparser legal graph; iterative strategies issue multiple passes. Weaviate’s filter execution sits under the same selectivity pressure; Milvus’s performance notes on a constrained budget and Qdrant’s large filtering guides both treat selectivity as a first-class performance input. Cloudflare’s limitations sections warn that not every metadata field is indexed the same way, so a predicate can be correct logically and still scan more than you expect.

Capacity planning therefore includes the filter distribution, not only vector count. A corpus where 90 percent of objects share one locale behaves differently from a corpus where each document has a unique ACL list. Log empty-result rates for filtered queries separately from unfiltered ones. Rising empty rates with stable unfiltered recall is the signature of filter-phase failure.

Self-query and agent-built filters generate predicates from natural language. That generation step can invent fields that do not exist or omit the tenant clause that security requires. Keep generated filters behind allow-lists of known properties, and reject queries that lack mandatory security predicates before they hit the index. Depth on constructing filters from language lives on the self-query leaf; enforcement still happens here as index-time constraints.

Stores that emphasise filtering include Weaviate, Qdrant, Pinecone and Milvus, each with different default strategies for combining predicates with ANN. Leading with Weaviate’s operator model does not make other engines wrong; it keeps the first example tied to Tier-1 docs. Re-read the engine’s filter-plus-vector section whenever you upgrade major versions, because pre-filter behaviour is an area vendors keep extending.

Geo filters, id filters and property filters compose differently across engines, but the RAG rule is stable: if the user is not allowed to see a region, document or field, the predicate must be mandatory on every retrieve path including eval. Optional filters for convenience facets can be omitted; security filters cannot. Weaviate’s geo-range and id examples in the filters docs (as of September 2026) are templates for that mandatory class of constraint.

Namespace features in some stores look like tenancy but behave like coarse filters or separate indexes depending on the product. Cloudflare Vectorize explicitly contrasts namespace versus metadata filtering. When a vendor says namespace, read whether missing the namespace parameter searches everything or errors. That single behaviour decides whether namespaces are isolation or convenience.

Empty filtered results should page operators differently from empty unfiltered results. The first suggests predicate or strategy bugs; the second suggests corpus or embedding bugs. Separate dashboards prevent months of tuning ef for a missing ACL field.

Keep filter fields small and typed. Large free-text metadata blobs defeat indexes and encourage contains-hacks that scan. Extract structured permissions at ingest instead of parsing prose ACLs at query time.

Metadata filtering makes vector search respect permissions, freshness and facets by constraining which neighbours are legal. Pre-filter and post-filter change whether selective predicates return empty sets or unsafe hits, and ANN graphs can lose effective recall when the legal subset is sparse. Store typed fields at ingest, measure filtered recall against an exact sample, and move customer boundaries to multi-tenancy when a missing filter must not be able to fail open.