mem0ai/mem0 · error · Error

Invalid filter key '${key}': only letters, digits, and under

Error message

Invalid filter key '${key}': only letters, digits, and underscores are allowed.

What it means

When building SQL WHERE clauses from SearchFilters in the pgvector store, each filter key (the payload field name) is validated against SAFE_IDENTIFIER_RE. A filter key with characters other than letters, digits, and underscores (or one that starts with a digit or is over 128 chars) is rejected. This guards the key interpolation into SQL, since keys cannot be sent as bind parameters.

Source

Thrown at mem0-ts/src/oss/src/vector_stores/pgvector.ts:24

const SAFE_IDENTIFIER_RE = /^[a-zA-Z_][a-zA-Z0-9_]{0,127}$/;

function validateIdentifier(
  name: string,
  label: string = "identifier",
): string {
  if (!SAFE_IDENTIFIER_RE.test(name)) {
    throw new Error(
      `Invalid ${label} '${name}': only letters, digits, and underscores are allowed, ` +
        `must start with a letter or underscore, and be at most 128 characters.`,
    );
  }
  return name;
}

function escapeFilterKey(key: string): string {
  if (!SAFE_IDENTIFIER_RE.test(key)) {
    throw new Error(
      `Invalid filter key '${key}': only letters, digits, and underscores are allowed.`,
    );
  }
  return key;
}

interface FilterResult {
  conditions: string[];
  values: any[];
  paramIndex: number;
}

const OPERATOR_SQL_MAP: Record<string, { template: string; numeric: boolean }> =
  {
    eq: { template: "payload->>'%KEY%' = $%IDX%", numeric: false },
    ne: { template: "payload->>'%KEY%' != $%IDX%", numeric: false },
    gt: { template: "(payload->>'%KEY%')::numeric > $%IDX%", numeric: true },
    gte: { template: "(payload->>'%KEY%')::numeric >= $%IDX%", numeric: true },

View on GitHub (pinned to 001c235229)

Solutions

  1. Rename the metadata key to snake_case/alphanumeric (letters, digits, underscore, starting with a letter or underscore) both in stored payloads and in the search filters
  2. Re-ingest the memories with normalized metadata keys, then filter on the new keys
  3. If keys come from external schemas, sanitize them at write time with a shared normalizeKey() function so reads and writes agree

Example fix

// before
const results = await memory.search('q', { filters: { 'user-id': 'u1' } });

// after
const results = await memory.search('q', { filters: { user_id: 'u1' } });
Defensive patterns

Strategy: validation

Validate before calling

const SAFE_IDENTIFIER_RE = /^[a-zA-Z_][a-zA-Z0-9_]{0,127}$/;
function normalizeFilters(filters: Record<string, any>) {
  const out: Record<string, any> = {};
  for (const [k, v] of Object.entries(filters)) {
    const key = k.replace(/[^a-zA-Z0-9_]/g, '_');
    if (!/^[a-zA-Z_]/.test(key)) throw new Error(`Filter key '${k}' cannot be normalized safely`);
    out[key] = v;
  }
  return out;
}

Type guard

const isSafeFilterKey = (k: string): boolean =>
  /^[a-zA-Z_][a-zA-Z0-9_]{0,127}$/.test(k);

Try / catch

try { await memory.search(q, { filters }); } catch (e) { if (e instanceof Error && e.message.includes('Invalid filter key')) { /* rename key, re-run */ } throw e; }

Prevention

When it happens

Trigger: Calling search/get with filters whose keys contain dots, hyphens, spaces, or start with a digit, e.g. { 'meta.type': 'fact' }, { 'user-id': 'u1' }, { '2024data': 'x' }, or keys built from arbitrary metadata field names written at add() time.

Common situations: Storing metadata keys copied from JSON APIs (dots in field names), using camelCase keys with special prefixes, or filtering on keys like 'run_id#2'. Often surfaces after data was ingested with arbitrary metadata keys.

Related errors


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