Multi-tenant retrieval-augmented generation has a deceptively simple failure mode: the answer can look correct while its supporting document belongs to another customer. A shared Qdrant collection is not unsafe by itself. The unsafe part is allowing a search to run without a tenant constraint, or trusting a tenant identifier supplied by the caller.
This guide builds a fail-closed pattern for n8n and Qdrant. Every document receives a server-derived tenant identifier at ingestion, and every retrieval repeats the same identifier as a metadata filter. A missing or malformed identity stops the workflow before vector search.
The security boundary
Similarity is not authorization. Embeddings place semantically related documents near one another, regardless of who owns them. A query for “renewal terms” can retrieve another customer’s contract if all tenants share a collection and the search has no ownership filter.
The safe data flow is:
- Authenticate the request outside the vector search.
- Derive
tenant_idfrom the verified identity, never from a query parameter or chat message. - Reject the request if the identity is missing or invalid.
- Write that exact
tenant_idinto document metadata during ingestion. - Require the same value in the Qdrant retrieval filter.
- Test that cross-tenant documents are absent, not merely ranked lower.
Qdrant documents three shared-deployment choices: partition by a payload field, dedicate shards, or combine both in a tiered design. Payload partitioning is a practical default for many small tenants, while dedicated collections or shards may be appropriate when stronger operational separation is required.
What n8n and Qdrant provide
n8n’s Qdrant Vector Store node supports a Metadata Filter in Get Many mode. Multiple fields are combined as an AND query, and metadata is attached when documents are inserted through a document loader.

On the database side, Qdrant can filter points by payload fields. Its multitenancy guidance recommends adding a tenant-identifying payload field to every point and applying that field to searches. Qdrant also supports a keyword payload index with is_tenant: true, available from Qdrant 1.11, to improve how same-tenant vectors are colocated. That setting is an optimization hint; the filter remains the authorization-critical part.
Use one canonical tenant identifier
Choose one normalized field such as tenant_id. Do not alternate between customer name, email domain, account slug, and an internal UUID. Display names change and can collide. A stable internal identifier is easier to validate and audit.
In n8n, place a validation step immediately after the trusted authentication step. The property path below is deliberately generic; replace verified_identity with the output produced by your authentication layer.
const tenantId = $json.verified_identity?.tenant_id;
if (
typeof tenantId !== 'string' ||
!/^[a-z0-9_-]{3,64}$/.test(tenantId)
) {
throw new Error('Invalid tenant_id');
}
return [{
json: {
...$json,
tenant_id: tenantId
}
}];
Do not fall back to a request-body tenant ID when this check fails. A fallback converts an authentication failure into a data-leak path.
Ingestion workflow: stamp ownership before embedding
A safe ingestion workflow can use these nodes:
- Webhook or queue trigger: receives a document-ingestion job.
- Authentication: verifies the caller and returns a trusted account identity.
- Validate Tenant: normalizes and validates the canonical tenant ID.
- Default Data Loader: loads the document and includes
tenant_idin metadata. - Embeddings: produces the vector.
- Qdrant Vector Store — Insert Documents: writes the vector, content, and metadata.
Keep the ownership metadata close to the document-loading step. If a workflow splits files into chunks, every chunk must inherit the same tenant ID. Also include a stable document_id so updates and deletions can target the correct tenant-owned document.
Tenant isolation and vector compatibility are separate contracts. If you change the embedding model, verify the vector dimension during both ingestion and retrieval; this guide to diagnosing embedding dimension mismatches in Pinecone and pgvector shows the dimension checks and safe migration pattern. Its database repair steps are Pinecone/pgvector-specific, so apply the compatibility principle—not those commands—to Qdrant.
| Field | Source | Purpose |
|---|---|---|
tenant_id |
Verified server-side identity | Mandatory retrieval boundary |
document_id |
Internal content record | Update, delete, and audit |
source |
Trusted ingestion context | Traceability |
visibility |
Server-side policy | Optional additional AND filter |
Retrieval workflow: fail closed before Qdrant
Use Qdrant Vector Store in Get Many mode for deterministic retrieval. Configure the query text and result limit, then add a Metadata Filter:
- Field:
tenant_id - Value:
={{ $json.tenant_id }}
If you also filter on visibility or a document state, n8n combines the fields with AND. That is useful, but the tenant condition must never be optional.
A conservative retrieval flow is:
- Verify identity and derive
tenant_id. - Stop on any missing or malformed value.
- Run Qdrant Get Many with the tenant metadata filter.
- Verify each returned item still carries the expected tenant ID.
- Only then pass retrieved text to the language model.
The post-query check is defense in depth. It should not replace the database filter, because unfiltered foreign text has already crossed the boundary by that point.
Optional Qdrant tenant index
For a shared collection with many tenants, create a keyword payload index for the field used to partition data. The Qdrant REST shape is:
PUT /collections/knowledge/index
{
"field_name": "tenant_id",
"field_schema": {
"type": "keyword",
"is_tenant": true
}
}
Use the actual payload path stored by your integration. Inspect one inserted point before creating the index: depending on the document-loader mapping, the field may be nested under a metadata object. The index and every filter must reference the same path.
Negative tests that prove isolation
A happy-path answer is not evidence of tenant isolation. Build a small two-tenant test corpus and run explicit negative cases.
| Test | Expected result |
|---|---|
| Tenant A asks for a unique phrase present only in A | A document is returned |
| Tenant A asks for a unique phrase present only in B | No B document is returned |
| Request omits authenticated tenant identity | Workflow stops before Qdrant |
| Caller sends a different tenant ID in the body | Body value is ignored or rejected |
Document chunk lacks tenant_id |
Ingestion fails; chunk is not stored |
| Batch contains mixed tenants | Split into isolated executions or reject the batch |
For each test, log the authenticated account ID, normalized tenant ID, workflow execution ID, and returned document IDs. Do not log document content or secrets merely to prove the filter worked.
Common unsafe patterns
- Using chat input as the tenant ID: prompt text is untrusted data, not an authorization decision.
- Filtering after retrieval: foreign content has already left the database boundary.
- Allowing an empty filter: a missing expression can silently become a collection-wide search.
- Stamping metadata only on the parent document: every stored chunk needs ownership metadata.
- Assuming a high similarity threshold protects tenants: relevance scores do not encode ownership.
- Connecting an unrestricted vector tool directly to an agent: prefer a deterministic retrieval workflow that enforces identity before the model can use results.
When metadata filtering is not enough
Payload filtering is an application-enforced boundary. It reduces cross-tenant retrieval risk when implemented consistently, but it is not the same as separate credentials, collections, clusters, encryption keys, or regulatory environments. Consider stronger separation for high-value tenants, incompatible retention rules, customer-managed keys, or workloads where one configuration error would be unacceptable.
Also restrict direct Qdrant access. If another workflow or service can query the same collection without the tenant filter, the overall system is still exposed. Treat the n8n workflow, Qdrant credentials, collection configuration, and audit trail as one security boundary.
Production checklist
- Tenant ID comes from verified server-side identity.
- Missing identity fails before vector search.
- Every chunk stores the same canonical tenant field.
- Every retrieval includes the tenant metadata filter.
- The stored payload path and filter path match.
- Cross-tenant negative tests run in CI or a staging workflow.
- Logs contain IDs and outcomes, not sensitive document text.
- Direct database access is restricted and reviewed.
- Higher-risk tenants use stronger physical or operational separation when needed.
The core rule is simple: derive ownership once from trusted identity, write it on every vector, and require it on every query. In a multi-tenant RAG system, that invariant matters more than the embedding model, similarity threshold, or prompt.
Official references: n8n Qdrant Vector Store node, Qdrant multitenancy guide, and Qdrant filtering documentation.


















