department=legal, region=us, status=published, or author=alice.
HydraDB has two metadata layers:
If a field will be scoped on every query, it belongs in
metadata and the database schema. If it’s ad-hoc or unique to one document, use additional_metadata instead.
1. Choose the right metadata layer
The
database field was formerly tenant_id and collection was formerly sub_tenant_id; the old names still work as deprecated aliases. Follow this for when to use database and collection.
2. How metadata filters run
For user-providedmetadata_filters, HydraDB uses a correctness-first pipeline:
- MongoDB source-id prefilter. Safe scalar tenant/additional metadata filters are resolved to matching
source_ids in MongoDB. - Scoped retrieval. Vector/BM25 retrieval searches only those source IDs.
- Post-filter correctness net. Hydrated chunks are checked again against the requested metadata. This also protects graph expansion and fallback/retry paths from leaking excluded sources.
Filter semantics
3. Define tenant metadata schema
Most of the time the defaults are right. When they aren’t, here’s where to start:- Plan scoping fields before first ingest. Schema is immutable, and undeclared scope keys are silently ignored at query time. If you’ll scope on it more than once, declare it.
- Pick
metadatafor hot paths,additional_metadatafor cold ones. Database-level scopes are pre-applied in the vector store; document-level scopes force a post-retrieval pass with over-fetch. - Use exact-equality only.
metadata_filtersare equality constraints across all categories: a scalar is an exact match, and a list matches any one of its values. Range, contains, or fuzzy matching belong in the query, query mode, or downstream reranking - not here. - Keep keys stable. Renaming a metadata key requires re-ingesting affected sources. Same goes for changing
enable_matchflags. - Ingested metadata is immutable. Once a source is indexed, its
metadataandadditional_metadatavalues are locked. To change them, re-ingest the source with the new values (useupsert: trueand the sameid). - Don’t substitute
metadata_filtersforcollection.metadata_filtersscopes results inside a partition. For partitioning by user, team, or workspace, usecollection.
4. Minimal working example
Two phases: set up metadata (declare the schema, then attach values at ingest), then scope at query.Step 1 - Create metadata
The schema lives at the database level; values land on each source at ingest time. Both happen before any query.Step 1a: Declare the schema at database creation
Schema field options
Limits and guardrails:
- Up to 32 custom tenant metadata fields.
- Up to 6 embedding-enabled fields per database.
enable_dense_embeddingandenable_sparse_embeddingeach count as one, so a field with both set counts as two.enable_matchdoes not count against this limit - only the embedding flags do. Exceeding it fails database creation with400before anything is provisioned. - Field names are unique case-insensitively.
- Dense/sparse embedding flags are only valid on
VARCHARfields. - Runtime
metadatavalues must match the declared type when the tenant has a schema. - Unknown
metadatakeys are rejected on ingest/edit when a non-empty tenant schema exists. - Each metadata layer has a byte budget per request - see Size limits.
Size limits
Every request that attaches metadata is checked against two caps, on ingest and on metadata edit alike:
Older spellings are still accepted, but not uniformly - which one works
depends on the endpoint:
Where an alias is accepted it is held to exactly the same cap as the canonical
field. Use the canonical names above and this never comes up.
The cap applies to the whole map, not to any one value, and it is measured on
the map’s compact JSON encoding in UTF-8 bytes. Three consequences worth planning
around:
- Keys and punctuation count. Quotes, colons, commas and braces are all part of the payload you are billed for.
- Bytes, not characters. Accented Latin characters cost 2 bytes, most CJK characters 3, and emoji 4.
- Budget in bytes from the start. A 950-character summary sounds comfortably under a 1 KiB cap, but with two small sibling keys it serializes to 1,015 bytes - 65 bytes of that is structure alone. Push the summary to 1,000 characters and the request is rejected at 1,065 bytes.
Document metadata: 1,015 bytes, just inside the 1 KiB cap
400 before anything is
ingested. The message names the offending field and reports both numbers, so you
can see exactly how far over you are:
PATCH /context/{id}/metadata
the same message is prefixed with invalid metadata edit:.
Filter size limits
The caps above bound the metadata you store.metadata_filters on
/query has its own, separate pair - these
bound what you send at query time and are unrelated to how much metadata a
source carries:
Measured the same way - compact JSON, UTF-8 bytes, keys and punctuation counted -
and the object total includes the nested
additional_metadata dict.
Both exist because every value in a list is expanded into the filter expression
sent to the vector store. The per-list cap catches one runaway list; the object
cap catches many individually-legal lists adding up. Twenty lists of 500 values
are each within the element cap but total roughly 127 KiB, so the object cap is
what rejects them.
Over either limit returns 400 before the query runs, naming the offending key
or the actual byte count:
Add schema fields later
You can add database metadata fields after database creation withPATCH /databases/{database}/metadata-schema. This is additive only:
- add new fields: yes
- delete fields: no
- change type/flags of existing fields: no
5. Attach metadata at ingest
For knowledge ingestion, sendmetadata and additional_metadata on each document_metadata[] item or app_knowledge[] item.
6. Query with metadata filters
Mix database-level (top-level) and document-level (nested) scopes in the samemetadata_filters object:
additional_metadata wins on conflicts.
7. Update metadata without re-ingesting
UsePATCH /context/{id}/metadata when you know the source ID and need to update metadata in place.
- The source must already exist.
collectionis required.- At least one of
tenant_metadata,additional_metadata, oraclis required. - The update is a merge/upsert: sent keys are inserted or overwritten; omitted keys are preserved.
document_metadatais not accepted on this endpoint; useadditional_metadata.- The same endpoint accepts
aclto change who may retrieve the source. Unlike metadata,aclreplaces rather than merges, and anacl-only body is a valid edit. See Access Control. enable_match-only tenant metadata updates are MongoDB-only and take effect for filters/listing.- If an edited tenant metadata field has
enable_dense_embeddingorenable_sparse_embedding, HydraDB synchronously syncs the relevant vector store lane and reportsvector_sync_required/vector_syncedin the response (themilvus_sync_required/milvus_syncedaliases are still emitted, deprecated).
upsert: true and the same source id. Upsert replaces the source payload and metadata supplied by ingestion.
8. Listing with metadata filters
UsePOST /context/list when you want to browse or page sources rather than run semantic retrieval:
/context/list also accepts legacy aliases tenant_metadata for metadata and document_metadata for additional_metadata.
9. Common mistakes
10. Advanced patterns
Stacked scopes with collection partitioning. Usecollection for the partition (per-user, per-workspace), and use metadata_filters to scope inside that partition. They’re complementary, not interchangeable. See Multi-Tenant.
Published vs draft. Add a status field with enable_match: true to your schema; tag every source with metadata.status = "draft" | "published"; pass metadata_filters: { status: "published" } on user-facing queries. Keeps work-in-progress out of customer answers automatically.
Multi-language corpora. Add a language field with enable_match: true; route each query to the right language by passing metadata_filters: { language: detect_language(query) }.
Schema-as-product. Treat database_metadata_schema as part of your data contract - review it like a database migration. The cost of getting it wrong (immutability + re-ingest) is real; the cost of getting it right is one extra meeting.
Related
- Knowledge - what attaches to knowledge sources
- Memories - metadata fields on memory items
- Multi-Tenant Support - partitioning vs scoping
- Query - how
metadata_filtersinteract with ranking - Create Database - API Reference - full
database_metadata_schemareference - Query - API Reference - full
metadata_filtersreference - List Context - source browsing filters
- Access Control - restricting who may retrieve a document, which is not a metadata filter
- Update Source Metadata - point metadata edits
- Update Metadata Schema - additive database schema changes
