medusajs/medusa · error · MedusaError

${message}

Error message

${message}

What it means

This is the filter-normalization error channel (filters.ts fail()): toSearchFilter/normalizeFacetRequests throw NOT_ALLOWED when a search filter cannot be translated to the provider's filter API — typically unsupported filter operators or values that don't match the planned field type (see the coerce/plan mapping).

Source

Thrown at packages/modules/search/src/providers/search-medusa/utils/filters.ts:7

import { SearchTypes } from "@medusajs/framework/types"
import { MedusaError } from "@medusajs/framework/utils"
import type { Filter } from "./api-types"
import { IndexPlan, PlannedField } from "./plan"

function fail(message: string): never {
  throw new MedusaError(MedusaError.Types.NOT_ALLOWED, message)
}

function coerce(value: unknown, planned?: PlannedField): unknown {
  if (value instanceof Date) {
    return value.toISOString()
  }
  if (planned?.is_date && typeof value === "string") {
    return new Date(value).toISOString()
  }
  return value
}

function globEscape(value: string): string {
  return value.replace(/([*?[\]\\])/g, "\\$1")
}

function sqlLikeToGlob(value: string): string {
  return value

View on GitHub (pinned to 5e06e544a2)

Solutions

  1. Coerce query params to the field's declared type before passing filters (numbers for numeric fields, ISO strings for dates)
  2. Filter only on fields present and filterable in the index definition
  3. Catch and log the MedusaError message — it names the exact field/value that failed normalization

Example fix

// before
filters: { price: { gte: req.query.minPrice } } // string from query string
// after
filters: {
  price: {
    gte: req.query.minPrice ? Number(req.query.minPrice) : undefined,
  },
}
Defensive patterns

Strategy: validation

Validate before calling

function coerceFilters(filters, spec) {
  const out = {}
  for (const [field, cond] of Object.entries(filters)) {
    if (!spec[field]) continue
    out[field] = Object.fromEntries(
      Object.entries(cond).map(([op, v]) => [
        op,
        spec[field] === "date" ? new Date(v).toISOString() : spec[field] === "number" ? Number(v) : v,
      ])
    )
  }
  return out
}

Type guard

const isCoercibleFilterValue = (v, type) =>
  type === "number" ? !Number.isNaN(Number(v)) : type === "date" ? !Number.isNaN(Date.parse(v)) : typeof v === "string"

Try / catch

try { await search(...) } catch (e) { if (e instanceof MedusaError && e.type === "not_allowed") { /* log e.message naming the bad filter, strip it, retry */ } throw e }

Prevention

When it happens

Trigger: Passing input.filters with an operator or value shape the provider cannot map onto a planned field: filtering a date field with a non-parsable value, filtering on an unknown/unplanned field, or a combination the provider's Filter model rejects.

Common situations: Storefront filter code sending raw query-string values (strings for numeric fields), filters referencing fields removed from the index after a settings change, or reusing filter payloads built for a different search provider (Meilisearch/Algolia syntax).

Related errors


AI-assisted analysis of medusajs/medusa@5e06e544a2 (2026-08-27). Data as JSON: /api/errors/2536160be439e49a. Report an issue: GitHub.