{"record":{"id":"84a218366fdbb008","repo":"mem0ai/mem0","slug":"key-filter-value-must-be-a-list-of-filter-dicts","errorCode":null,"errorMessage":"${key} filter value must be a list of filter dicts, got ${typeof value}","messagePattern":"(.+?) filter value must be a list of filter dicts, got (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"mem0-ts/src/oss/src/vector_stores/qdrant.ts","lineNumber":235,"sourceCode":"\n    // Normalize $or/$not/$and → OR/NOT/AND and deduplicate\n    const normalized: Record<string, any> = {};\n    for (const [key, value] of Object.entries(filters)) {\n      const normKey = KEY_MAP[key] || key;\n      if (!(normKey in normalized)) {\n        normalized[normKey] = value;\n      }\n    }\n\n    const must: (QdrantCondition | QdrantFilter)[] = [];\n    const should: (QdrantCondition | QdrantFilter)[] = [];\n    const mustNot: (QdrantCondition | QdrantFilter)[] = [];\n\n    for (const [key, value] of Object.entries(normalized)) {\n      // Handle logical operators\n      if (key === \"AND\" || key === \"OR\" || key === \"NOT\") {\n        if (!Array.isArray(value)) {\n          throw new Error(\n            `${key} filter value must be a list of filter dicts, got ${typeof value}`,\n          );\n        }\n        for (let i = 0; i < value.length; i++) {\n          const item = value[i];\n          if (\n            typeof item !== \"object\" ||\n            item === null ||\n            Array.isArray(item)\n          ) {\n            throw new Error(\n              `${key} filter list item at index ${i} must be a dict, got ${typeof item}`,\n            );\n          }\n        }\n\n        if (key === \"AND\") {\n          for (const sub of value) {","sourceCodeStart":217,"sourceCodeEnd":253,"githubUrl":"https://github.com/mem0ai/mem0/blob/001c235229be8795e3834520467bd0d661ed8f34/mem0-ts/src/oss/src/vector_stores/qdrant.ts#L217-L253","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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 })) }"],"exampleFix":"// before\nconst r = await vs.search(vec, { filters: { AND: { user_id: 'u1' } } });\n\n// after\nconst r = await vs.search(vec, { filters: { AND: [ { user_id: 'u1' } ] } });","handlingStrategy":"type-guard","validationCode":"function normalizeLogicalFilters(filters: any): any {\n  const out: any = {};\n  for (const [k, v] of Object.entries(filters)) {\n    if (['AND','OR','NOT'].includes(k) && v && typeof v === 'object' && !Array.isArray(v)) {\n      out[k] = [v]; // wrap single dict into a list\n    } else {\n      out[k] = v;\n    }\n  }\n  return out;\n}","typeGuard":"const isFilterDictList = (v: unknown): v is Record<string, any>[] =>\n  Array.isArray(v) && v.length > 0 && v.every(i => i && typeof i === 'object' && !Array.isArray(i));","tryCatchPattern":"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; }","preventionTips":["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)"],"tags":["qdrant","search-filters","logical-operators","validation","typescript"],"backgroundTag":null,"analyzedSha":"001c235229be8795e3834520467bd0d661ed8f34","analyzedAt":"2026-08-15T01:55:42.685Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}