Skip to main content
Several stores sell the same master variant, so prices and stock can’t live on the variant alone. Mercur keeps one price set per variant and tags each price row with the offer that owns it. Stock goes the other way: each offer gets its own inventory items, linked to the offer and never to the variant. The shared price set of the Linen shirt M variant holds price rows for three offers; each row carries an offer_id rule, and the two rows for offer_01JD8QA form a quantity tier. Behind it, that offer links to its own inventory items. The shared price set of the Linen shirt M variant holds price rows for three offers; each row carries an offer_id rule, and the two rows for offer_01JD8QA form a quantity tier. Behind it, that offer links to its own inventory items.

Prices on the shared price set

An offer has no price set of its own. Its prices are rows on the master variant’s PriceSet, 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.
These two rows give the offer a quantity tier. Each price accepts 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 into setPricingContext 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 the offer_id rule
  • Owned price left out: removes the price and its link
  • id owned by another offer: the run fails with Price … does not belong to offer …
Removed prices are deleted, not soft-deleted. The offerPricesUpserted hook payload carries previous_prices, which is the only place the old values can still be read.

Inventory linked to the offer

Offer stock lives in Medusa InventoryItem 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.
variant.inventory_items is empty for offers. Read stock through the offer, never through the variant.
The link has two read paths in query.graph:
  • inventory_items: the InventoryItem rows themselves, without the pivot columns
  • inventory_item_link: the pivot rows, with required_quantity and a nested inventory_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.