mastra-ai/mastra · error · TypeError

Invalid metadata filter key "${key}".

Error message

Invalid metadata filter key "${key}".

What it means

Each key in a metadata filter must be a non-empty string up to MAX_METADATA_KEY_LENGTH, match SAFE_METADATA_KEY_PATTERN, and not be in the DISALLOWED_METADATA_KEYS set (reserved keys). Keys that are too long, contain unsafe characters, or collide with reserved names are rejected so filters remain safe and unambiguous across storage backends.

Source

Thrown at packages/core/src/storage/utils.ts:116

const MAX_METADATA_KEY_LENGTH = 128;
const DISALLOWED_METADATA_KEYS = new Set(['__proto__', 'prototype', 'constructor']);

export function validateStorageMetadataFilter(
  metadata: StorageMetadataFilter | undefined,
): StorageMetadataFilter | undefined {
  if (metadata === undefined) return undefined;
  if (!metadata || typeof metadata !== 'object' || Array.isArray(metadata)) {
    throw new TypeError('Metadata filter must be an object.');
  }

  const entries = Object.entries(metadata);
  for (const [key, value] of entries) {
    if (
      key.length > MAX_METADATA_KEY_LENGTH ||
      !SAFE_METADATA_KEY_PATTERN.test(key) ||
      DISALLOWED_METADATA_KEYS.has(key)
    ) {
      throw new TypeError(`Invalid metadata filter key "${key}".`);
    }
    if (
      value !== null &&
      typeof value !== 'string' &&
      typeof value !== 'boolean' &&
      !(typeof value === 'number' && Number.isFinite(value))
    ) {
      throw new TypeError(
        `Invalid metadata filter value for key "${key}". Values must be string, finite number, boolean, or null.`,
      );
    }
  }

  return entries.length > 0 ? metadata : undefined;
}

export function storageMessageMatchesMetadataFilter(
  content: unknown,

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Shorten the key within the allowed max length
  2. Use only safe identifier-like characters: letters, digits, underscore, hyphen (alphanumerics only to be safe)
  3. Rename reserved/disallowed keys to a non-conflicting name (e.g. prefix 'meta_')
  4. Sanitize user-supplied keys before use: replace disallowed chars, e.g. key.replace(/[^a-zA-Z0-9_-]/g, '_')

Example fix

// before
metadata: { 'user.profile.id': 42 }
// after
metadata: { 'user-profile-id': 42 }
Defensive patterns

Strategy: validation

Validate before calling

const SAFE_KEY = /^[a-zA-Z0-9_-]+$/;
function isValidMetadataKey(k: string, maxLen = 64): boolean {
  return k.length > 0 && k.length <= maxLen && SAFE_KEY.test(k);
}
const clean = Object.fromEntries(
  Object.entries(raw).map(([k, v]) => [k.replace(/[^a-zA-Z0-9_-]/g, '_').slice(0, 64), v])
);

Type guard

function hasSafeKeys(f: Record<string, unknown>): boolean {
  return Object.keys(f).every(k => /^[a-zA-Z0-9_-]+$/.test(k));
}

Try / catch

try {
  return await storage.listTraces({ metadata: filter });
} catch (e) {
  if (e instanceof TypeError && e.message.includes('metadata filter key')) {
    const key = /key "([^"]+)"/.exec(e.message)?.[1];
    logger.warn('Invalid metadata key dropped', { key });
    const { [key!]: _, ...rest } = filter;
    return await storage.listTraces({ metadata: rest });
  }
  throw e;
}

Prevention

When it happens

Trigger: Using a key longer than the max length, keys with special characters like '$', '.', spaces, or unicode outside the safe pattern, or reserved/disallowed keys (e.g. a key named like a storage field).

Common situations: Copying raw user input or URL fragments into metadata keys; using Mongo-style operators ('$gte', '$or') expecting rich queries; using dotted paths ('user.id') expecting nested matching.

Related errors


AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30). Data as JSON: /api/errors/fc0e5c1506174faa. Report an issue: GitHub.