mem0ai/mem0 · error · Error
Invalid filter key: ${JSON.stringify(key)}
Error message
Invalid filter key: ${JSON.stringify(key)} What it means
validateFilter() in the Elasticsearch vector store throws when a filter key is not a plain identifier matching ^[a-zA-Z_][a-zA-Z0-9_]*$. Filter keys are interpolated directly into metadata.${key} field paths in ES queries, so keys with dots, brackets, spaces, or special characters could alter query semantics (or enable query injection), and are rejected before reaching Elasticsearch.
Source
Thrown at mem0-ts/src/oss/src/vector_stores/elasticsearch.ts:33
password?: string;
collectionName: string;
embeddingModelDims: number;
dimension?: number;
useSsl?: boolean;
caCerts?: string;
verifyCerts?: boolean;
autoCreateIndex?: boolean;
headers?: Record<string, string>;
}
const SAFE_FILTER_KEY = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
// Mirrors the Python provider's _validate_filter. Filter keys are interpolated
// into `metadata.${key}` field paths, so reject anything that is not a plain
// identifier and reject non-scalar values before they reach the query.
function validateFilter(key: string, value: unknown): void {
if (typeof key !== "string" || !SAFE_FILTER_KEY.test(key)) {
throw new Error(`Invalid filter key: ${JSON.stringify(key)}`);
}
if (
typeof value !== "string" &&
typeof value !== "number" &&
typeof value !== "boolean"
) {
throw new Error(
`Filter value for ${JSON.stringify(key)} must be string, number, or boolean`,
);
}
}
export class ElasticsearchDB implements VectorStore {
private client!: Client;
private readonly config: ElasticsearchConfig;
private readonly collectionName: string;
private readonly dimension: number;
private readonly autoCreateIndex: boolean;View on GitHub (pinned to 001c235229)
Solutions
- Use flat keys that are plain identifiers: { user_id: 'u1' } — no dots, hyphens, or spaces.
- Rename metadata fields at write time (insert payload keys must also be identifiers) so lookups stay valid.
- If filter keys come from user input, sanitize/whitelist them before calling search.
- Avoid wrapping filters in an extra object level; pass the key/value map directly.
Example fix
// before
await es.search(query, 5, { 'user.id': 'u1', 'agent-id': 'a1' });
// after
await es.search(query, 5, { user_id: 'u1', agent_id: 'a1' }); Defensive patterns
Strategy: validation
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(`Invalid filter keys: ${badKeys.join(', ')}`); Type guard
const SAFE_KEY = /^[a-zA-Z_][a-zA-Z0-9_]*$/; const hasSafeFilterKeys = (f: unknown): f is Record<string, string | number | boolean> => typeof f === 'object' && f !== null && Object.keys(f).every((k) => SAFE_KEY.test(k));
Prevention
- Restrict metadata field names to plain identifiers (letters, digits, underscore, no leading digit) at insert time.
- Never build filter keys from raw user input without whitelisting.
- Keep filters flat — no nested objects or dotted paths.
When it happens
Trigger: Calling search/list/delete with filters whose keys contain dots (e.g. 'user.id'), hyphens ('agent-id'), spaces, leading digits ('2fa'), or when the key is not a string at all. Nested object filter shapes like { metadata: { user_id: 'x' } } also produce invalid keys.
Common situations: Using metadata keys with dots or hyphens that Elasticsearch would otherwise interpret as nested field paths; passing a nested/wrapped filter object instead of a flat one; dynamic filter keys sourced from user input containing arbitrary characters.
Related errors
- Filter value for ${JSON.stringify(key)} must be string, numb
- Invalid filter key: {key!r}
- Top-level entity parameters [${invalidKeys.join(", ")}] are
- Invalid ${name}: cannot be empty or whitespace-only. Provide
- Invalid ${name}: cannot contain whitespace. Provide a valid
AI-assisted analysis of mem0ai/mem0@001c235229 (2026-08-15).
Data as JSON: /api/errors/d1844bbd7e68a174.
Report an issue: GitHub.