mem0ai/mem0 · error · Error

Filter value for '${key}' must be a string, number, or boole

Error message

Filter value for '${key}' must be a string, number, or boolean, got ${Array.isArray(value) ? "an array" : "an object"}.

What it means

Filter leaf values become OpenSearch term/terms/range/wildcard query parameters. Accepting arbitrary objects would let a caller inject raw query DSL (e.g. a term object with boost or case_insensitive, changing query semantics or exfiltrating internals), so the store enforces a scalar allow-list (string/number/boolean/null) mirroring the Python SDK's _validate_filter. Non-scalar leaves throw with the offending key and actual type.

Source

Thrown at mem0-ts/src/oss/src/vector_stores/opensearch.ts:640

    return clauses.length === 1 ? clauses[0] : { bool: { filter: clauses } };
  }

  private payloadField(key: string, keyword: boolean): string {
    if (key.startsWith("payload.")) {
      return keyword && !key.endsWith(".keyword") ? `${key}.keyword` : key;
    }

    const field = `payload.${key}`;
    return keyword ? `${field}.keyword` : field;
  }

  // Filter values become OpenSearch term/terms/range/wildcard leaves. Allowing
  // an object here lets a caller inject raw query parameters (e.g. a `term`
  // object form with `boost`/`case_insensitive`), so reject non-scalar leaves.
  // Mirrors the Python SDK's `_validate_filter` scalar allow-list (PR #5986).
  private assertScalarValue(key: string, value: any): void {
    if (value !== null && typeof value === "object") {
      throw new Error(
        `Filter value for '${key}' must be a string, number, or boolean, got ` +
          `${Array.isArray(value) ? "an array" : "an object"}.`,
      );
    }
  }

  private assertScalarArray(key: string, value: any): void {
    if (!Array.isArray(value)) {
      throw new Error(`Filter value for '${key}' must be an array.`);
    }
    value.forEach((item) => this.assertScalarValue(key, item));
  }
}

View on GitHub (pinned to 001c235229)

Solutions

  1. Pass plain scalars for scalar operators: { user_id: 'a' } or { memory: { contains: 'text' } }.
  2. For membership use the dedicated in operator with an array: { tag: { in: ['a','b'] } } — do not inline DSL objects.
  3. Validate external filter input against a schema (zod/etc.) before handing it to search.

Example fix

// before
filters = { user_id: { value: 'alice', boost: 2 } };

// after
filters = { user_id: 'alice' };
Defensive patterns

Strategy: type-guard

Validate before calling

for (const [field, ops] of Object.entries(fieldFilters)) {
  for (const [op, v] of Object.entries(ops || {})) {
    if (v !== null && typeof v === 'object' && !['in','nin'].includes(op)) {
      throw new TypeError(`Field '${field}' operator '${op}' expects a scalar`);
    }
  }
}

Type guard

const isScalar = (v: unknown): v is string | number | boolean | null =>
  v === null || ['string', 'number', 'boolean'].includes(typeof v);

Prevention

When it happens

Trigger: Passing { user_id: { term: { value: 'a', boost: 2 } } } or any object/array where a scalar is expected, e.g. { user_id: ['a','b'] } where the operator expects a scalar (contains, eq).

Common situations: Attempts to hand-tune OpenSearch relevance via query DSL inside filters; accidentally passing an array to a scalar operator; filters built from unvalidated user input where a client sends an object.

Related errors


AI-assisted analysis of mem0ai/mem0@001c235229 (2026-08-15). Data as JSON: /api/errors/72f148f64a6cfe85. Report an issue: GitHub.