#16452·medusa

[Bug]: column "product_id" does not exist when querying product.options (*options) — 2.16+ ProductProductOption pivot mapping

Author: NedissaCreated Aug 14, 2026Updated Sep 16, 2026
Labelstype: bugStalerequires-team

Bug report

Querying products with the options relation expanded (e.g. fields=id,*options on /store/products or /admin/products, or the admin dashboard's product option UI) throws:

{"type":"invalid_data","message":"column p0.product_id does not exist"}

The exact alias in the error varies by call site (seen both p0.product_id and o3.product_id from the admin dashboard), but the failure is consistent: any query expanding Product.options fails.

Root cause (as far as I can tell)

Since 2.16.0, @medusajs/product defines two parallel relations between Product and ProductOption:

javascript
// product.js
options: model.manyToMany(() => ProductOption, {
  pivotEntity: () => ProductProductOption,
}),
product_options: model.hasMany(() => ProductProductOption, {
  mappedBy: "product",
}),
javascript
// product-product-option.js (pivot entity, @since 2.16.0)
const ProductProductOption = model.define("ProductProductOption", {
  id: model.id({ prefix: "prodopt" }).primaryKey(),
  product: model.belongsTo(() => Product, { mappedBy: "options" }),
  product_option: model.belongsTo(() => ProductOption, { mappedBy: "products" }),
  values: model.manyToMany(() => ProductOptionValue, {
    pivotEntity: () => ProductProductOptionValue,
  }),
});

The product_product_option table itself has a correct product_id column (confirmed via \d product_product_option and a manual join, both work fine). But MikroORM's auto-generated join for the older options many-to-many relation (via pivotEntity) appears to build the query against the wrong table alias, producing a product_id reference on an alias that doesn't have that column — likely a collision between the options (manyToMany + pivotEntity) and product_options (hasMany, mappedBy: "product") relations pointing at the same pivot table.

Reproduction

bash
curl 'http://localhost:9000/store/products?limit=1&fields=id,*options' \
  -H 'x-publishable-api-key: <key>'
# => {"type":"invalid_data","message":"column p0.product_id does not exist"}

# Works fine without *options:
curl 'http://localhost:9000/store/products?limit=1&fields=id,title'
curl 'http://localhost:9000/store/products?limit=1&fields=id,*variants'
curl 'http://localhost:9000/store/products?limit=1&fields=id,*variants.prices'

# Manual SQL confirms the data/schema is correct:
SELECT po.id, po.title, ppo.product_id
FROM product_option po
LEFT JOIN product_product_option ppo ON ppo.product_option_id = po.id
LIMIT 10;
-- returns correct rows

Also reproducible in the Admin dashboard UI (product option editing surfaces the same error client-side, e.g. column o3.product_id does not exist).

Environment

  • @medusajs/medusa: 2.19.0 (also present in 2.14.2, so this predates at least a few minor versions — possibly present since the 2.16.0 product_options refactor)
  • Node: v22.22.1
  • PostgreSQL: 18.4
  • Package manager: pnpm 10.11.1

Expected behavior

Expanding *options on a product query should return the product's options via the product_product_option pivot table without error, same as expanding *variants or *categories works today.

Additional notes

  • db:migrate reports the product module as fully up to date; this is not a missing-migration issue.
  • The options field is not currently required by our storefront, so this is not blocking for us, but it does break admin UI functionality that relies on the option relation.