
Narrow Your Vector Search to the Right Docs: Qdrant Payload Filtering for RAG
Chris Harper
3 min read
Aug 5, 2026 · 04:05 UTC
Most RAG pipelines over-fetch and post-filter. Qdrant pushes filter conditions into the HNSW graph itself — retrieve only matching docs at the same latency as unfiltered search.
What you'll be able to do after this:
- Store structured metadata (source, date, category) alongside every vector and query it efficiently
- Write
must/should/must_notfilter clauses that run inside the HNSW traversal, not after it - Scope your RAG pipeline's context window to only docs that pass both semantic relevance and business rules
Without metadata filtering, your LLM's context fills with the most semantically similar chunks regardless of whether they're from the right source, the right time period, or the right document category. Filtering fixes that — but post-filtering (retrieve 50, then discard 45) bloats latency and hurts recall at scale. Qdrant's payload filtering runs inside the graph traversal itself.
Step-by-step
1. Create a collection and index your filter fields first
Create payload indexes before ingesting data. Without an index Qdrant falls back to a full scan; with one, filtered queries approach unfiltered latency.
from qdrant_client import QdrantClient
from qdrant_client.models import Distance, VectorParams
client = QdrantClient("localhost", port=6333)
client.create_collection(
collection_name="docs",
vectors_config=VectorParams(size=1536, distance=Distance.COSINE),
)
# Index filter fields BEFORE inserting data
client.create_payload_index("docs", field_name="category", field_schema="keyword")
client.create_payload_index("docs", field_name="published_ts", field_schema="integer")
client.create_payload_index("docs", field_name="source", field_schema="keyword")
2. Upsert vectors with metadata payloads
from qdrant_client.models import PointStruct
client.upsert(
collection_name="docs",
points=[
PointStruct(
id=1,
vector=embed("Firewall configuration best practices"),
payload={
"category": "security",
"source": "internal_wiki",
"published_ts": 20260601, # YYYYMMDD int for range queries
"title": "Firewall Config Guide",
},
),
],
)
3. Search with a filter
from qdrant_client.models import Filter, FieldCondition, MatchValue, Range
hits = client.search(
collection_name="docs",
query_vector=embed(user_query),
query_filter=Filter(
must=[
FieldCondition(key="category", match=MatchValue(value="security")),
FieldCondition(key="published_ts", range=Range(gte=20260101)),
]
),
limit=5,
)
must = AND. should = OR. must_not = exclusion. Nest them arbitrarily: "from source A or B, published after date X, not tagged draft."
Two gotchas:
-
If you use LangChain's Qdrant integration, it wraps your payload inside a
metadatakey automatically. Usekey="metadata.category"in your filter, notkey="category"— the mismatch causes silent zero-result queries. -
Range filters over
integerfields need your date stored as an integer (e.g.,20260601). Store timestamps as Unix epoch integers if you need finer granularity.
Next step: Once you have payload filtering working, check out Qdrant's LLM-powered filter automation — let the model extract filter conditions from natural language queries instead of hard-coding them.
Sources: A Complete Guide to Filtering in Vector Search — Qdrant · Qdrant Payload Filtering in Python — YouTube · LLM-Powered Filter Automation — Qdrant Docs · Qdrant Python Tutorial — Medium