[Bug]: Customer-group price lists never apply — rules are written as `customer_group_id` but matched as `customer.groups.id`
Bug report
A price list whose rule targets a customer group is silently ignored during price calculation. Customers in the group keep paying the default price — no error, no warning.
The cause is an attribute-name mismatch between the two halves of the feature:
| attribute | |
|---|---|
written by the admin dashboard, POST /admin/price-lists, and createPriceListsWorkflow |
customer_group_id |
matched by PricingRepository.calculatePrices after flattenObjectToKeyValuePairs flattens the pricing context |
customer.groups.id |
price_list_rule.attribute is compared verbatim against the flattened context key (plr.attribute = ? in packages/modules/pricing/src/repositories/pricing.ts), so the two never meet.
Minimal reproduction
The context built by the store product route (packages/medusa/src/api/utils/middlewares/products/set-pricing-context.ts) flattens like this:
flattenObjectToKeyValuePairs({
region_id: "reg_1",
customer: { groups: [{ id: "cusgroup_1" }] },
})
// => { region_id: "reg_1", "customer.groups.id": ["cusgroup_1"] }
// ^ not "customer_group_id"End to end against a running server:
- Create a customer group and a customer in it.
- Create a variant priced at 1000.
POST /admin/price-listswith{ "type": "override", "status": "active", "rules": { "customer_group_id": ["<groupId>"] }, "prices": [{ "variant_id": "<id>", "currency_code": "usd", "amount": 780 }] }- As that customer:
GET /store/products?region_id=…&fields=*variants.calculated_price
Expected: calculated_amount 780, is_calculated_price_price_list true.
Actual: calculated_amount 1000, is_calculated_price_price_list false.
Changing only the rule attribute fixes it:
UPDATE price_list_rule SET attribute = 'customer.groups.id' WHERE attribute = 'customer_group_id';After that the same request returns 780.
The same failure happens through the cart — line items are created at the default price for customers in the group.
Two things that make this hard to notice:
- The admin dashboard still renders the price list correctly ("Customer groups · Wholesale"), because the display path resolves groups from the rule value regardless of the attribute name.
GET /admin/price-listsechoes back whatever attribute is stored, so the API response looks consistent either way.
Note that customer.groups.id is the attribute the rest of v2 already uses for this concept — see rule-attributes-map.ts for promotions, and the customer.groups.id field lists in the cart and order workflows.
System information
Medusa version (including plugins): @medusajs/medusa 2.21.0, @medusajs/pricing 2.21.0, @medusajs/core-flows 2.21.0
Node.js version: 20.20.2
Database: PostgreSQL 15.19
Operating system: macOS (Darwin 25.4.0)
Browser (if relevant): n/a
Source: medusajs/medusa