metadata_filters on POST /query keeps only the sources whose values match, such as department = legal or status = published, before ranking runs.
In this guide:
- Declare a field and filter on it: create the schema, ingest with metadata, and query with a filter.
- The two layers:
metadataandadditional_metadata, and how a filter runs. - Filter operators:
equals,contains, andcontains_any, and how to store several values in one field. - Size limits: caps on stored metadata and on the filter you send.
- Add schema fields later.
- Update metadata without re-ingesting.
- List sources with metadata filters.
- Schema field reference, common mistakes, and related pages.
1. Declare a field and filter on it
Three requests: create the database with a metadata schema, ingest a source that carries values for those fields, then query withmetadata_filters. The example declares department, priority, and a summary_label field that is also searchable as text.
Create the database with a metadata schema: Python, TypeScript, or cURL
Create the database with a metadata schema: Python, TypeScript, or cURL
Ingest a source with metadata: Python, TypeScript, or cURL
Ingest a source with metadata: Python, TypeScript, or cURL
GET /databases/status to report infra.ready_for_ingestion: true before ingesting.Send metadata and additional_metadata on each app_knowledge[] item, or on each document_metadata[] item when you upload files. For type=memory, the memories field is already a JSON string, and each memory item’s metadata and additional_metadata are plain objects inside it; do not stringify them a second time.Query with metadata filters: Python, TypeScript, or cURL
Query with metadata filters: Python, TypeScript, or cURL
GET /context/status to report completed for auth-controls-001 in the default collection before running this query.Top-level keys filter on metadata. Keys nested under additional_metadata filter on the free-form layer. Both can appear in one request.What each property means
2. The two layers
- HydraDB resolves the
metadataandadditional_metadatafilters to the matching source IDs. - It runs vector and keyword retrieval only over those sources.
- It rechecks every retrieved passage against the filter. The recheck also keeps graph expansion and retry paths from leaking excluded sources.
additional_metadata filter that cannot be resolved up front is checked after retrieval, with about 3x over-fetch to compensate.
A few fields worth declaring from the start:
status: tag every sourcedraftorpublished, and send{"status": {"equals": "published"}}on user-facing queries to keep work in progress out of customer answers.language: route each query to its language with a filter on the detected language.
metadata and filter on it in each query. Use separate collections only for data that must never meet in one search.
3. Filter operators
Each top-level key inmetadata_filters takes an operator object naming the comparison:
equals works on every declared type, so {"priority": {"equals": 7}} is valid on an INT64 field. contains, contains_any, and arrays need a VARCHAR field; sent for a declared field of another type, they are rejected with 400.
Operator forms, and what each returns
Operator forms, and what each returns
attendee_emails values:additional_metadata: operators apply to top-level keys only. Inside additional_metadata, use a bare value for an exact match or an array to match any listed value. An operator placed there is not rejected; it is read as an exact match against a stored object, so the query returns 200 with an empty result.
What returns 400, and what does not: a known operator with the wrong value type, several operators in one object, or a null value is rejected with 400. A misspelled operator, such as {"contian": "x"}, is not rejected; it is read as a filter for a stored object with that key and matches nothing.
Reserved key names: equals, contains, and contains_any are read as operators. This only matters for a JSON field that stores an object whose keys are all drawn from those words: {"contains": "x"} is read as the operator, {"contains": "a", "equals": "b"} is rejected with 400, and {"contains": "a", "other": 1} stays an ordinary exact-match filter. To match such an object, rename the nested key or the field.
Bare forms: filters without an operator still work but are deprecated, because the comparison is inferred from the JSON shape. A bare value behaves as equals, a bare array as contains_any, and a one-element array as contains, so {"emails": "a@x"} and {"emails": ["a@x"]} return different results. document_metadata is still accepted as a legacy alias for additional_metadata; when both are present, additional_metadata wins on conflicts.
Store several values in one field
A declared schema field holds a single value. To store several values in one field, declare it asVARCHAR and join the values with commas, then filter for one member with contains.
Store comma-joined values and filter for one member
Store comma-joined values and filter for one member
data_type: "array"is not accepted on a declared field: declaring one is rejected with400.- An array value for a declared
VARCHARfield is also rejected:metadata field "attendee_emails" must be of type string, got array. Join the values yourself. - The comma is the separator: a value that itself contains a comma will not match as expected. Use a field per value, or a different value format, if your values can contain commas.
- Size the field for the whole joined string:
max_lengthdefaults to1024, its maximum is65535, and it cannot be raised after the field is created, so declare it large enough up front. equalscompares the entire joined string: it is rarely what you want on a multi-value field. Usecontains.
4. Size limits
Stored metadata
Every request that attaches metadata is checked against two caps, on ingest and on metadata edit alike:- Keys and punctuation count: quotes, colons, commas, and braces all count toward the byte limit.
- 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.
400 before anything is ingested. The message names the offending field and reports both numbers. On PATCH /context/{id}/metadata the same message is prefixed with invalid metadata edit:.
A map just inside the 1 KiB cap, and the 400 it returns when it grows
A map just inside the 1 KiB cap, and the 400 it returns when it grows
additional_metadata for the short values you filter on.
Filter size limits
metadata_filters on /query has its own pair of caps. They bound what you send at query time, not how much metadata a source carries:
additional_metadata object, and it is measured after operator objects are reduced to their values, so {"contains": "x"} counts as ["x"] and the operator keyword costs nothing.
Both limits 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.
Exceeding either limit returns 400 before the query runs, naming the offending key or the actual byte count.
The 400 messages for an oversized filter
The 400 messages for an oversized filter
5. Add schema fields later
PATCH /databases/{database}/metadata-schema adds fields to an existing database. It is additive only: fields are added, never deleted, retyped, or reflagged. The response returns added_fields directly, without the success and data envelope, and a field you resend with an identical definition is skipped.
Add a status field to an existing database: Python, TypeScript, or cURL
Add a status field to an existing database: Python, TypeScript, or cURL
- Searchable fields are declared at creation: fields added later cannot turn on dense or sparse search; the endpoint rejects
enable_dense_embeddingandenable_sparse_embedding. - Existing fields are fixed: renaming a key means re-ingesting the affected sources, or creating a new database with the corrected schema.
- Undeclared keys match nothing: a database with a schema rejects undeclared
metadatakeys on ingest and on edit, so no source ever holds them, and a filter on such a key returns nothing. The exceptions areconnector_idandprovider, which connectors write on every synced object. If you will filter on a field more than once, declare it.
6. Update metadata without re-ingesting
UsePATCH /context/{id}/metadata when you know the source ID and need to change its metadata in place. The body uses database_metadata for the schema-backed layer (the shorter metadata spelling from ingest is not accepted here) and additional_metadata for the free-form layer.
- The source must already exist: this endpoint does not create sources, and
collectionis required. - At least one of
database_metadata,additional_metadata, oraclis required:tenant_metadatais a deprecated alias fordatabase_metadata;document_metadatais rejected. - Metadata merges: sent keys are inserted or overwritten; omitted keys are preserved.
aclis the exception: when sent, it replaces the stored list, and anacl-only body is a valid edit. See Access Control. - The edit is visible to filters and listing right away: if no edited field has an embedding flag, the vector store is untouched and the response reports
vector_sync_required: false. - Searchable fields sync synchronously: if an edited field has
enable_dense_embeddingorenable_sparse_embedding, HydraDB refreshes that field’s search lane and reportsvector_sync_requiredandvector_syncedin the response (themilvus_sync_requiredandmilvus_syncedaliases are still emitted, deprecated). Such a field rejectsnull, because it would leave stale metadata vectors behind.
upsert: true and the same source id. Upsert replaces the source payload and the metadata supplied by ingestion.
7. List sources with metadata filters
UsePOST /context/list to browse or page sources rather than run semantic retrieval. Its filters object takes metadata, additional_metadata, and source_fields, each as bare key-value pairs. source_fields filters on the source’s own fields: title, type, description, url, timestamp, and for app sources app_provider, app_kind, app_external_id, and app_parent_id. Pair app_external_id or app_parent_id with app_provider, because a Jira issue key and a Linear ID can collide.
List legal sources by Alice from Slack: Python, TypeScript, or cURL
List legal sources by Alice from Slack: Python, TypeScript, or cURL
/context/list also accepts the legacy aliases tenant_metadata for metadata and document_metadata for additional_metadata; the current name wins on conflicts. It takes the same acl field as /query to list as one person. See Access Control.
Schema field reference
Each entry indatabase_metadata_schema on Create Database, and in add_fields on Update Metadata Schema, takes these options:
- Up to 32 custom metadata fields per database.
- 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. Exceeding the limit fails database creation with400before anything is provisioned. - Field names are unique case-insensitively.
- Embedding flags are only valid on
VARCHARfields. - Values must match the declared type when the database has a schema.
- Unknown
metadatakeys are rejected on ingest and edit when the database has a non-empty schema. - Each metadata layer has a byte budget per request; see Size limits.
Common mistakes
Related
- Knowledge: what attaches to knowledge sources
- Memories: metadata fields on memory items
- Multi-tenancy: partitioning with collections, which filters do not replace
- Query: how
metadata_filtersinteracts with ranking - Access Control: restricting who may retrieve a document, which is not a metadata filter
- Create Database (API Reference): full
database_metadata_schemareference - Query (API Reference): full
metadata_filtersreference - List Context (API Reference): source browsing filters
- Update Source Metadata (API Reference): point metadata edits
- Update Metadata Schema (API Reference): additive database schema changes
