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
1const { data: orders } = await query.graph({2  entity: "order",3  fields: ["id", "metadata"],4  filters: {5    // ❌ This doesn't work6    metadata: {7      carePlanId: "123",8    },9    // ❌ Neither does this10    "metadata.carePlanId": "123",11  },12})

Workarounds#

Option 1: Filter After Retrieval#

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

Code
1const { data: orders } = await query.graph({2  entity: "order",3  fields: ["id", "metadata"],4})5
6const filteredOrders = orders.filter(7  (order) => order.metadata?.carePlanId === "123"8)

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
1import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"2import { Modules } from "@medusajs/framework/utils"3import { IOrderModuleService } from "@medusajs/framework/types"4
5export const GET = async (6  req: MedusaRequest,7  res: MedusaResponse8) => {9  const orderModuleService: IOrderModuleService = req.scope.resolve(10    Modules.ORDER11  )12
13  const orders = await orderModuleService.listOrders({14    metadata: {15      carePlanId: "123",16    },17  })18
19  res.json({ orders })20}

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

src/api/orders/custom-metadata-search/route.ts
1import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"2import {3  Modules,4  ContainerRegistrationKeys,5} from "@medusajs/framework/utils"6import { IOrderModuleService } from "@medusajs/framework/types"7
8export const GET = async (9  req: MedusaRequest,10  res: MedusaResponse11) => {12  const orderModuleService: IOrderModuleService = req.scope.resolve(13    Modules.ORDER14  )15  const query = req.scope.resolve(16    ContainerRegistrationKeys.QUERY17  )18
19  // Step 1: Use the module service to get order IDs matching the metadata filter20  const orderIds = await orderModuleService.listOrders({21    metadata: {22      carePlanId: "123",23    },24  }, {25    select: ["id"],26  })27
28  // Step 2: Use Query to get related data for those orders29  const { data: orders } = await query.graph({30    entity: "order",31    fields: ["id", "metadata", "customer.id"],32    filters: {33      id: orderIds.map((o) => o.id),34    },35  })36
37  res.json({ orders })38}

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
⇧↵