mastra-ai/mastra · error · TypeError

Metadata filter must be an object.

Error message

Metadata filter must be an object.

What it means

validateStorageMetadataFilter requires the metadata filter to be a plain object (not undefined, null, an array, or a primitive). Metadata filters are key/value maps used to filter records in storage; anything else cannot be interpreted as a filter map, so a TypeError is thrown before hitting storage.

Source

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

      return JSON.parse(input);
    } catch {
      return input;
    }
  }
  // For anything else (number, boolean, etc.), return empty object
  return {};
}

const SAFE_METADATA_KEY_PATTERN = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
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(

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Pass a plain object like { region: 'us', env: 'prod' }
  2. Convert arrays of pairs: Object.fromEntries([['region','us']])
  3. Parse string filters from query params with JSON.parse or your own parser before calling
  4. Guard: if (filter && !Array.isArray(filter)) before calling

Example fix

// before
storage.listTraces({ metadata: 'tenant=acme' });
// after
storage.listTraces({ metadata: { tenant: 'acme' } });
Defensive patterns

Strategy: validation

Validate before calling

function isPlainObject(v: unknown): v is Record<string, unknown> {
  return typeof v === 'object' && v !== null && !Array.isArray(v);
}
const filter = isPlainObject(rawFilter) ? rawFilter : {};

Type guard

function isMetadataFilter(v: unknown): v is Record<string, string | number | boolean | null> {
  return typeof v === 'object' && v !== null && !Array.isArray(v);
}

Try / catch

try {
  return await storage.listTraces({ metadata: filter });
} catch (e) {
  if (e instanceof TypeError && e.message.includes('Metadata filter')) {
    logger.warn('Dropping invalid metadata filter', { filter });
    return await storage.listTraces({});
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling metadataFilter(null), metadataFilter([]) (an array is an object but rejected), or metadataFilter('region=us') — any non-plain-object value passed where a metadata filter is expected (e.g. in storage list/query APIs).

Common situations: Spreading optional params into a query and passing a variable that ended up null after JSON parsing; accidentally passing an array of key/value pairs instead of an object; string-encoded filters from URL query params not being parsed.

Related errors


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