Prices on the shared price set
An offer has no price set of its own. Its prices are rows on the master variant’sPriceSet, and each row carries a price rule with the attribute offer_id set to the offer’s id.
When an offer is created, ensureVariantPriceSetsStep gives the variant a price set if it has none. The workflow then adds each price with the offer_id rule merged into any rules you pass, and links every new price to the offer through the offer ↔ price link. That link is what offer.prices reads.
min_quantity, max_quantity, and extra rules. You never write the offer_id rule yourself: the workflow adds it.
How the right price is picked
Carts resolve offer prices through Medusa’s pricing context. Mercur hooks intosetPricingContext on addToCartWorkflow, updateLineItemInCartWorkflow, and refreshCartItemsWorkflow and adds offer_id with the ids of the offers in the cart.
The pricing module then matches only the rows whose offer_id rule fits. If none of the items being added resolves to an offer, addToCartWorkflow fails with Every cart line item must resolve to an offer_id.
Replacing an offer’s prices
Updating prices replaces the offer’s own rows and leaves every other offer on the variant alone.upsertOfferPricesWorkflow does the work, and updateOffersWorkflow runs it when an entry has a prices array.
The incoming array is the full new list:
- Row with an
id: updates that price in place - Row without an
id: adds a new price with theoffer_idrule - Owned price left out: removes the price and its link
idowned by another offer: the run fails withPrice … does not belong to offer …
Inventory linked to the offer
Offer stock lives in MedusaInventoryItem records linked to the offer. The offer ↔ inventory_item link is many-to-many and its table, offer_inventory_item, has a required_quantity column (default 1).
One offer can draw on several inventory items, which makes it a kit. Ordering 1 unit of the offer reserves required_quantity units of each linked item.
The link has two read paths in query.graph:
inventory_items: theInventoryItemrows themselves, without the pivot columnsinventory_item_link: the pivot rows, withrequired_quantityand a nestedinventory_item
Inventory created with the offer
createOffersWorkflow creates fresh inventory items for every offer. Each inventory_items entry becomes one new item with optional starting stock_levels, linked to the offer and to the store.
Every offer needs at least one entry. The new item takes the variant’s title, and when the offer has a single item with required_quantity 1, it also copies the variant’s weight, dimensions, hs_code, mid_code, origin_country, and material.
The offer row’s manage_inventory and allow_backorder flags drive the stock check. When manage_inventory is true and allow_backorder is false, adding the offer to a cart confirms required_quantity × quantity for each linked item. To change which items back an existing offer, use batchOfferInventoryItemsWorkflow.