Metadata Filtering in Query

Currently, Query doesn't support filtering by nested JSON properties in metadata fields using query.graph.

Why This Limitation Exists#

Metadata fields in Medusa are stored as JSON/JSONB columns in PostgreSQL. While PostgreSQL supports querying JSON fields, Query's abstraction layer doesn't currently expose this functionality through the standard filtering interface.

For example, this won't work:

Code
const { data: orders } = await query.graph({
  entity: "order",
  fields: ["id", "metadata"],
  filters: {
    // ❌ This doesn't work
    metadata: {
      carePlanId: "123",
    },
    // ❌ Neither does this
    "metadata.carePlanId": "123",
  },
})

Workarounds#

Option 1: Filter After Retrieval#

For infrequent queries, retrieve the data with Query first, then filter the results in memory:

Code
const { data: orders } = await query.graph({
  entity: "order",
  fields: ["id", "metadata"],
})

const filteredOrders = orders.filter(
  (order) => order.metadata?.carePlanId === "123"
)

This approach is less efficient for large datasets since all records are loaded into memory first.

Option 2: Use Module Service Directly#

You can also bypass Query and use the module service directly to leverage PostgreSQL's JSONB querying capabilities. The downside to this approach is that you can't retrieve data across modules.

For example, to filter orders by metadata using the Order Module Service:

src/api/orders/custom-metadata-search/route.ts
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
import { Modules } from "@medusajs/framework/utils"
import { IOrderModuleService } from "@medusajs/framework/types"

export const GET = async (
  req: MedusaRequest,
  res: MedusaResponse
) => {
  const orderModuleService: IOrderModuleService = req.scope.resolve(
    Modules.ORDER
  )

  const orders = await orderModuleService.listOrders({
    metadata: {
      carePlanId: "123",
    },
  })

  res.json({ orders })
}

You can also combine this with Query to retrieve related data across modules. For example:

src/api/orders/custom-metadata-search/route.ts
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
import {
  Modules,
  ContainerRegistrationKeys,
} from "@medusajs/framework/utils"
import { IOrderModuleService } from "@medusajs/framework/types"

export const GET = async (
  req: MedusaRequest,
  res: MedusaResponse
) => {
  const orderModuleService: IOrderModuleService = req.scope.resolve(
    Modules.ORDER
  )
  const query = req.scope.resolve(
    ContainerRegistrationKeys.QUERY
  )

  // Step 1: Use the module service to get order IDs matching the metadata filter
  const orderIds = await orderModuleService.listOrders({
    metadata: {
      carePlanId: "123",
    },
  }, {
    select: ["id"],
  })

  // Step 2: Use Query to get related data for those orders
  const { data: orders } = await query.graph({
    entity: "order",
    fields: ["id", "metadata", "customer.id"],
    filters: {
      id: orderIds.map((o) => o.id),
    },
  })

  res.json({ orders })
}

Was this page helpful?
Ask Bloom
For assistance in your development, use Claude Code Plugins or Docs MCP server in Cursor, VSCode, etc...FAQ
What is Medusa?
How can I create a module?
How can I create a data model?
How do I create a workflow?
How can I extend a data model in the Product Module?
Recipes
How do I build a marketplace with Medusa?
How do I build digital products with Medusa?
How do I build subscription-based purchases with Medusa?
What other recipes are available in the Medusa documentation?
Chat is cleared on refresh
Line break
⇧↵