Skip to main content
Metadata is structured data attached to Knowledge and Memories. Use it when you already know a hard constraint before retrieval runs, such as 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-provided metadata_filters, HydraDB uses a correctness-first pipeline:
  1. MongoDB source-id prefilter. Safe scalar tenant/additional metadata filters are resolved to matching source_ids in MongoDB.
  2. Scoped retrieval. Vector/BM25 retrieval searches only those source IDs.
  3. 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.
That means a valid filter with no matching sources returns an empty result set; HydraDB does not silently widen it into an unfiltered search.

Filter semantics

metadata_filters are hard constraints, not semantic hints. A filter like { "mood": "happy" } requires an exact stored value; it does not expand to related values like “joyful” or “cheerful”. To search metadata text semantically, declare a VARCHAR field with enable_dense_embedding and include the concept in the main query.

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 metadata for hot paths, additional_metadata for 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_filters are equality constraints across all categories. 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_match flags.
  • Ingested metadata is immutable. Once a source is indexed, its metadata and additional_metadata values are locked. To change them, re-ingest the source with the new values (use upsert: true and the same id).
  • Don’t substitute metadata_filters for collection. metadata_filters scopes results inside a partition. For partitioning by user, team, or workspace, use collection.

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.
  • Field names are unique case-insensitively.
  • Dense/sparse embedding flags are only valid on VARCHAR fields.
  • Runtime metadata values must match the declared type when the tenant has a schema.
  • Unknown metadata keys are rejected on ingest/edit when a non-empty tenant schema exists.

Add schema fields later

You can add database metadata fields after database creation with PATCH /databases/{database}/metadata-schema. This is additive only:
  • add new fields: yes
  • delete fields: no
  • change type/flags of existing fields: no
Adding fields updates the stored database schema and MongoDB filter indexes. Existing Milvus collections are not altered/backfilled for newly added dense/sparse metadata lanes yet. If you need a new metadata field to participate in semantic/BM25 metadata search for existing data, create a new database/schema and re-ingest, or confirm the current platform migration path with support.

5. Attach metadata at ingest

For knowledge ingestion, send metadata and additional_metadata on each document_metadata[] item or app_knowledge[] item.
For type=memory, the memories multipart field is already JSON-stringified, and each memory item’s metadata is currently validated as a JSON-encoded string. Keep additional_metadata as an object. For knowledge ingestion (document_metadata and app_knowledge), metadata is an object.

6. Query with metadata filters

Mix database-level (top-level) and document-level (nested) scopes in the same metadata_filters object:
Use the legacy alias only when maintaining older clients:
If both aliases are present and both are objects, additional_metadata wins on conflicts.

7. Update metadata without re-ingesting

Use PATCH /context/sources/{source_id}/metadata when you know the source ID and need to update metadata in place.
Behavior:
  • The source must already exist.
  • collection is required.
  • At least one of tenant_metadata or additional_metadata is required.
  • The update is a merge/upsert: sent keys are inserted or overwritten; omitted keys are preserved.
  • document_metadata is not accepted on this endpoint; use additional_metadata.
  • enable_match-only tenant metadata updates are MongoDB-only and take effect for filters/listing.
  • If an edited tenant metadata field has enable_dense_embedding or enable_sparse_embedding, HydraDB synchronously syncs the relevant vector store lane and reports vector_sync_required / vector_synced in the response (the milvus_sync_required / milvus_synced aliases are still emitted, deprecated).
For full document/content replacement, re-ingest with upsert: true and the same source id. Upsert replaces the source payload and metadata supplied by ingestion.

8. Listing with metadata filters

Use POST /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. Use collection 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.