mem0ai/mem0 · error · Error
${key} filter value must be a list of filter dicts, got ${ty
Error message
${key} filter value must be a list of filter dicts, got ${typeof value} What it means
Logical operators AND, OR, NOT in Qdrant filters must map to an array of sub-filter objects (mirroring Qdrant's must/should/must_not lists). If the value next to AND/OR/NOT is not an array (e.g. a plain object, string, or number), this error is thrown before any API call.
Source
Thrown at mem0-ts/src/oss/src/vector_stores/qdrant.ts:235
// Normalize $or/$not/$and → OR/NOT/AND and deduplicate
const normalized: Record<string, any> = {};
for (const [key, value] of Object.entries(filters)) {
const normKey = KEY_MAP[key] || key;
if (!(normKey in normalized)) {
normalized[normKey] = value;
}
}
const must: (QdrantCondition | QdrantFilter)[] = [];
const should: (QdrantCondition | QdrantFilter)[] = [];
const mustNot: (QdrantCondition | QdrantFilter)[] = [];
for (const [key, value] of Object.entries(normalized)) {
// Handle logical operators
if (key === "AND" || key === "OR" || key === "NOT") {
if (!Array.isArray(value)) {
throw new Error(
`${key} filter value must be a list of filter dicts, got ${typeof value}`,
);
}
for (let i = 0; i < value.length; i++) {
const item = value[i];
if (
typeof item !== "object" ||
item === null ||
Array.isArray(item)
) {
throw new Error(
`${key} filter list item at index ${i} must be a dict, got ${typeof item}`,
);
}
}
if (key === "AND") {
for (const sub of value) {View on GitHub (pinned to 001c235229)
Solutions
- Wrap the logical value in an array: { AND: [ { a: 1 } ] } even for a single condition
- When building filters dynamically, always map conditions into an array: { AND: conditions.map(c => ({ [c.key]: c.value })) }
Example fix
// before
const r = await vs.search(vec, { filters: { AND: { user_id: 'u1' } } });
// after
const r = await vs.search(vec, { filters: { AND: [ { user_id: 'u1' } ] } }); Defensive patterns
Strategy: type-guard
Validate before calling
function normalizeLogicalFilters(filters: any): any {
const out: any = {};
for (const [k, v] of Object.entries(filters)) {
if (['AND','OR','NOT'].includes(k) && v && typeof v === 'object' && !Array.isArray(v)) {
out[k] = [v]; // wrap single dict into a list
} else {
out[k] = v;
}
}
return out;
} Type guard
const isFilterDictList = (v: unknown): v is Record<string, any>[] => Array.isArray(v) && v.length > 0 && v.every(i => i && typeof i === 'object' && !Array.isArray(i));
Try / catch
try { await vs.search(vec, { filters }); } catch (e) { if (e instanceof Error && e.message.includes('must be a list of filter dicts')) { /* wrap value in [], retry */ } throw e; } Prevention
- Always wrap logical-operator values in arrays, even single conditions
- Type filters as { AND?: FilterDict[] } so TS rejects non-arrays
- Validate filter JSON from external clients with a schema (zod/json-schema)
When it happens
Trigger: filters: { AND: { a: 1 } } (object instead of array), { OR: 'x' }, { NOT: 5 }, or code that assigns a single condition directly to a logical key instead of wrapping it in a list.
Common situations: Refactoring nested filters and forgetting the array wrapper; conditionally building filters where a single sub-filter is not wrapped in []; merging user-supplied JSON filters of unknown shape.
Related errors
- ${key} filter list item at index ${i} must be a dict, got ${
- Cannot mix range operators (${ops.filter((o) => rangeOps.inc
- Unsupported filter operator(s) for field '${key}': ${ops.joi
- ${key} filter requires a non-empty list
- $not filter requires a non-empty list
AI-assisted analysis of mem0ai/mem0@001c235229 (2026-08-15).
Data as JSON: /api/errors/84a218366fdbb008.
Report an issue: GitHub.