mem0ai/mem0 · error · Error
Invalid filter key: ${JSON.stringify(key)}
Error message
Invalid filter key: ${JSON.stringify(key)} What it means
createFilter() interpolates filter keys into a Milvus boolean expression as metadata["<key>"], so a key containing characters outside [a-zA-Z_][a-zA-Z0-9_]* could alter or inject expression syntax. Keys are validated against SAFE_FILTER_KEY and rejected with JSON.stringify(key) so the offending key is visible in the error. This mirrors the Python provider's _create_filter.
Source
Thrown at mem0-ts/src/oss/src/vector_stores/milvus.ts:236
/**
* Filter keys are interpolated straight into the expression, so restrict them
* to safe identifiers (same rule as the Python provider) to block injection.
*/
private static readonly SAFE_FILTER_KEY = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
/**
* Build a Milvus boolean filter expression from a flat filters object.
* Mirrors the Python `_create_filter` (equality only, AND-combined): validate
* each key, escape string values (backslash first, then double-quote), and
* reject value types Milvus can't compare against a scalar field.
*/
private createFilter(filters?: SearchFilters): string | undefined {
if (!filters || Object.keys(filters).length === 0) return undefined;
const operands: string[] = [];
for (const [key, value] of Object.entries(filters)) {
if (value === undefined || value === null) continue;
if (!Milvus.SAFE_FILTER_KEY.test(key)) {
throw new Error(`Invalid filter key: ${JSON.stringify(key)}`);
}
if (value === "*") {
// Wildcard - match any value. Milvus has no direct wildcard, so skip
// the clause rather than emitting a literal `== "*"` that matches
// nothing. Mirrors the Python provider (#6187) and the chroma/pinecone
// stores.
continue;
}
if (typeof value === "string") {
// Escape backslashes before quotes so a value can't break out of the
// string literal (order matters, exactly as in the Python provider).
const escaped = value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
operands.push(`(metadata["${key}"] == "${escaped}")`);
} else if (typeof value === "number" || typeof value === "boolean") {
operands.push(`(metadata["${key}"] == ${value})`);
} else {
throw new Error(
`Filter value for ${JSON.stringify(key)} must be a string, number, or boolean, got ${typeof value}`,View on GitHub (pinned to 001c235229)
Solutions
- Rename filter keys to match ^[a-zA-Z_][a-zA-Z0-9_]*$ (snake_case is the repo convention: user_id, agent_id, run_id).
- Map external keys to safe keys before calling search: { user_id: external['user-id'] }.
- Sanitize keys at your API boundary: key.replace(/[^a-zA-Z0-9_]/g, '_') with a leading-underscore fix if it starts with a digit.
Example fix
// before
store.search(q, 5, { 'user-id': 'u1' }); // throws: Invalid filter key
// after
store.search(q, 5, { user_id: 'u1' }); Defensive patterns
Strategy: type-guard
Validate before calling
const SAFE_KEY = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
const badKeys = Object.keys(filters || {}).filter((k) => !SAFE_KEY.test(k));
if (badKeys.length) throw new Error(`Unsafe Milvus filter keys: ${badKeys.join(', ')}`); Type guard
const isSafeFilterKey = (k: string): boolean => /^[a-zA-Z_][a-zA-Z0-9_]*$/.test(k);
function assertSafeMilvusFilters(f: Record<string, unknown>): void {
for (const k of Object.keys(f)) if (!isSafeFilterKey(k)) throw new Error(`Invalid filter key: ${k}`);
} Try / catch
try { await store.search(q, 5, filters); }
catch (e) {
if (e instanceof Error && e.message.startsWith('Invalid filter key')) {
// sanitize keys (replace [^a-zA-Z0-9_] with '_') and retry once
} else throw e;
} Prevention
- Use snake_case metadata keys (user_id, agent_id, run_id) — the repo-wide convention.
- Map external/kebab-case keys to safe keys at your API boundary.
- Reject user-supplied filter keys early with the same regex.
When it happens
Trigger: Passing filters with keys like 'user-id', 'user id', 'user.id', 'user:id', keys starting with a digit ('1key'), empty-string keys, or non-string keys after Object.entries coercion; derived keys built from user input containing dashes or dots.
Common situations: Using hyphenated or dotted metadata field names (common in JSON from external systems); slugifying filter keys to kebab-case; migrating metadata schemas from systems that allow arbitrary key characters.
Related errors
- AND filter value must be a list of filter dicts, got ${typeo
- OR filter value must be a list of filter dicts, got ${typeof
- NOT filter value must be a list of filter dicts, got ${typeo
- Invalid filter key: ${JSON.stringify(key)}
- AND filter value must be a list of filter dicts, got ${typeo
AI-assisted analysis of mem0ai/mem0@001c235229 (2026-08-15).
Data as JSON: /api/errors/29f86c28b0206d65.
Report an issue: GitHub.