Medusa.js field notes
Fix Medusa storefront, pricing, inventory, orders, and workflows with a script
A working library of the problems that hit real Medusa.js (v2) stores, across storefront access, regions and pricing, inventory and stock, cart and checkout, orders and fulfillment, promotions, module links, and background jobs, and how to detect or repair each one with a small Python or Node.js script on the Medusa Admin API. Every guide has a diagram of the problem, a diagram of the fix, full code in both languages, tests, and links to the source docs.
Storefront and API access
3 guidesStorefront shows no products
A Store API request with no publishable API key returns an empty list. Detect the missing key and wire it to a sales channel.
DiagnosticPublishable key not linked to a sales channel
The key exists but points at no sales channel, so products stay hidden. Link it.
DiagnosticStore requests blocked by CORS
The storefront gets CORS errors from the Store API. Probe the endpoints and report the gap.
DiagnosticRegions, pricing, and currency
4 guidesProduct has no price in a region
A variant has no price in the region currency, so it cannot be bought there. Find the gaps.
ReconcilerPrice list not applied at checkout
A sale price list is set but the cart still shows list price. Check its status and dates.
DiagnosticWrong currency shown for a region
The region currency and the shown price currency disagree. Flag the mismatched prices.
DiagnosticTax-inclusive pricing shows wrong totals
Tax-inclusive and tax-exclusive prices are mixed, so totals look off. Detect the mismatch.
DiagnosticInventory and stock
5 guidesVariant not purchasable, no inventory level
A tracked variant has no inventory level at the location, so it reads out of stock. Create it.
ReconcilerSales channel not linked to a stock location
The channel has no stock location, so nothing is in stock there. Link a location.
DiagnosticStuck reservations after cancelled carts
Cancelled carts left reservations that still hold stock. Release the stale ones.
ReconcilerOversold variant goes negative
Stock dropped below zero after a race. Reset the oversold variants to a real count.
RepairInventory not managed, item never sells out
manage_inventory is off, so a variant never runs out. Detect them and turn it on.
DiagnosticCart and checkout
4 guidesCart completion fails, no payment provider
The region has no payment provider, so checkout cannot finish. Find the empty regions.
RepairNo shipping option for the cart
A service zone gap leaves the cart with no shipping option. Detect the uncovered regions.
DiagnosticAbandoned carts pile up
Old carts that never completed clutter the database. Report the stale ones for cleanup.
ReconcilerStale cart prices after a price change
A price change did not reach open carts. Flag the carts still holding the old price.
ReconcilerOrders and fulfillment
4 guidesOrder stuck not fulfilled past SLA
A paid order sat unfulfilled past your promise. Tag the overdue ones for review.
DiagnosticPayment captured but order not paid
The capture went through but the order still reads unpaid. Reconcile the payment status.
ReconcilerFulfillment without a tracking number
A fulfillment shipped with no tracking number. Flag the ones missing it.
DiagnosticRefund not reflected on the order
A refund in the provider never updated the order. Record it so totals tie out.
ReconcilerPromotions and campaigns
3 guidesPromotion not applying
A promotion never applies because its rules do not match the cart. Check the rules.
DiagnosticCampaign budget exceeded but still applies
A campaign past its budget keeps discounting. Detect and stop it.
DiagnosticDuplicate promotion codes
Two promotions share a code and collide. Report the duplicates.
DiagnosticModules, links, and data
5 guidesLinked data missing after a link migration
A module link was added but never migrated, so linked data is missing. Detect it.
DiagnosticOrphaned records after a link delete
Deleting a link left dangling records behind. Find and clean the orphans.
ReconcilerVariant options mismatch blocks creation
A variant is missing an option value, so creation fails. Detect the incomplete variants.
DiagnosticDuplicate product handles from import
An import created products with the same handle. Report the conflicts.
ReconcilerBroken product image links
Product images point at files that no longer load. Detect the broken ones.
DiagnosticEvents, workflows, and jobs
4 guidesEvents lost without the Redis event bus
In production the in-memory event bus drops events, so side effects are missed. Reconcile them.
ReconcilerWorkflow left half done
A workflow failed mid-run and compensation did not undo it. Find the half-done records.
ReconcilerScheduled job did not run
A scheduled job silently skipped, so its work is missing. Detect the gap and catch up.
ReconcilerSubscriber fails silently on order placed
An order.placed subscriber threw and was swallowed. Find orders missing the side effect.
DiagnosticMigration and housekeeping
2 guidesCustomer & Auth
4 guidesGuest checkout creates a duplicate customer on registration
Registering with a prior guest order email makes a second customer row instead of updating has_account; script finds email duplicates.
soonOrders stay linked to the guest record after registration
Orders remain attached to the guest customer row instead of the new registered account sharing that email; script re-links by email.
soonAdmin invite fails when the email already belongs to a customer
Inviting an admin user throws identity already exists if a store customer used that email; script cross checks identities before invite.
soonFailed invite acceptance leaves an orphaned auth identity
A failed accept invite workflow reverts the invite to pending but keeps the auth identity, blocking re-acceptance; script finds and clears it.
soonInventory & Reservations
7 guidesFulfillment sometimes leaves a stale inventory reservation
Fulfilling an order occasionally fails to delete its reservation row, locking stock; script finds and removes reservations tied to closed orders.
soonMulti-part product reservations go negative after fulfillment
Bundled product variants under-reserve then swing reserved_quantity negative on fulfillment; script flags negative reserved_quantity rows.
soonExisting reservation blocks fulfillment of its own order
When reserved quantity equals stocked quantity, admin refuses to fulfill even the order that holds the reservation; script frees orphaned reservations.
soonReservation records have no order id to trace back to
The order id field on inventory reservations is always blank, so scripts must join through line items instead of a direct key.
soonReservation update event never fires
RESERVATION_ITEM_UPDATED does not trigger on manual or system changes, so stock-sync subscribers miss updates; script diffs quantity against last synced value.
soonmanage_inventory false variants still lose stock quantity
Variants opted out of tracking still get stocked_quantity decremented; script diffs untracked variants against their level history for drift.
soonReserve inventory step fails even with backorders allowed
Cart completion throws at the reservation step for backorder enabled variants at zero or negative stock, leaving carts stuck; script detects and reruns.
soonStock Locations & Sales Channels
3 guidesInventory decremented at the wrong stock location
A sale reduces stock at the product's first linked location rather than the one tied to the order's sales channel; script compares expected vs actual location.
soonCheckout blocked for carts split across stock locations
A cart with items stocked at two locations under the same channel fails to complete though both are in stock; script finds carts stuck at reservation.
soonPublishable key with multiple channels reports zero stock
Store API shows zero available stock instead of summing locations when a key maps to multiple sales channels; script compares admin vs store quantities.
soonPayments & Refunds
8 guidesCustom provider capture skips creating the order transaction
A provider returning captured status never creates the order transaction, so paid_total stays wrong; script recomputes paid_total from provider records.
soonOutstanding amount does not update after the first refund
Only the first refund updates outstanding_amount, letting later refunds repeat the same amount unnoticed; script diffs summary against real provider refunds.
soonCustom provider capture leaves outstanding amount desynced
After a custom provider capture, outstanding_amount and paid_total drift from actual payments, corrupting later refunds; script recalculates and flags drift.
soonRefund rejected on a captured order showing zero outstanding
The refund API blocks valid refunds citing no outstanding balance even though the payment was fully captured; script flags and issues the refund directly.
soonOnly one refund per order or payment ever succeeds
Later refund attempts silently fail after the first, leaving refunded total below what admin recorded; script sums refund transactions against the order total.
soonSub-cent rounding mislabels a paid order as partially captured
A rounding gap under one cent between collection and captured amount flags fully paid orders as partially_captured; script recomputes and clears near-zero deltas.
soonNew payment collection ignores amounts already captured
After a price edit, a new payment collection shows the full new total instead of the remaining outstanding balance; script recomputes outstanding and reconciles.
soonStripe capture succeeds but no Medusa order is created
Funds are captured in Stripe but the cart never completes to an order due to a webhook race; script reconciles Stripe charges against Medusa orders.
soonOrder Edits & Totals
5 guidesOrder edit after capture computes the refund direction backwards
Swapping to a cheaper item during an order edit prompts to collect more money instead of refunding the difference; script recomputes the diff correctly.
soonEditing an order cancels its payment collection and blocks capture
Since v2.7, editing quantity or price sets payment_status to not_paid and cancels the collection permanently; script finds canceled collections with active edits.
soonDraft orders cannot get a payment collection created
Draft orders have no cart id, so the create payment collection step fails and blocks manual payment; script detects draft orders stuck without one.
soonOrder summary drops tax from totals, triggering bogus refunds
v2.10.1 excludes tax from accounting_total and pending_difference, auto-issuing partial refunds equal to the tax; script recomputes totals with tax included.
soonOrder edit blocked by a stuck active order change record
Editing fails with an active order change is required after upgrade because a prior OrderChange never reached a terminal status; script finds and clears it.
soonFulfillment & Returns
3 guidesFulfillment status stays delivered after a full return and refund
Processing a complete return and refund never flips fulfillment_status to returned; script cross checks return records against fulfillment_status.
soonCannot cancel a fulfillment once stock has gone negative
Canceling a fulfillment for an item with negative inventory_quantity throws a not enough stock error; script scans location levels for negative quantities.
soonFulfillment created event never fires for untracked items
Fulfillments on orders with manage_inventory false items never emit fulfillment_created, so shipment notifications silently skip; script checks for missing side effects.
soonPricing & Promotions
9 guidesBuy X get Y promotions fail to apply during cart updates
Buyget type promotions do not apply in the update cart promotions workflow even when conditions are met; script re-evaluates rules against cart contents.
soonCampaign budget usage never increments for Buy X Get Y
Redeeming a buy X get Y promotion does not update the campaign budget's used counter, letting spend exceed budget; script recomputes usage from applied orders.
soonPromotion ignores its sales channel condition and applies anyway
A promotion scoped to one sales channel applies store wide regardless of the cart's channel; script checks applied promotions against cart.sales_channel_id.
soonShipping discount uses a stale shipping amount after cart changes
Percentage off shipping promotions keep discounting an outdated shipping total when cart items change; script recomputes the promo against the current total.
soonDraft orders reject valid promotion codes
Applying a promotion code to a draft order fails even though the same code works on a regular cart; script applies the code via API and flags rejections.
soonA price list suppresses all default variant prices once active
calculated_price ignores default prices entirely once any price list exists, hiding correct fallback pricing; script compares default vs calculated price.
soonPrice list keeps serving prices past its end date
Storefront keeps charging an expired price list price after end_date passes; script flags price lists with a past end date still returned by calculated_price.
soonMultiple customer group membership breaks price list resolution
Customers belonging to more than one group get the default price instead of their group's price list override; script checks quoted price per test customer.
soonRegion scoped price ignored in favor of currency only price
A price explicitly scoped to a region_id is skipped for a currency only price, causing the wrong charge amount; script cross checks price rules vs served price.
soonWorkflows & Background Jobs
5 guidesLong running workflow executions get stuck in invoking state
workflow_execution rows for long workflows stay invoking and sometimes flap in and out of the table; script polls for entries stuck past expected TTL.
sooncontinueOnPermanentFailure skips compensation and leaves partial state
Steps configured to continue on permanent failure never roll back, leaving orphaned created records; script cross references order state against expected outputs.
soonScheduled jobs execute twice per configured interval
A job configured with one cron interval fires twice per tick, duplicating side effects like emails or exports; script counts run timestamps for duplicates.
soonScheduled jobs stop firing after long uptime
Long running instances silently stop triggering any cron job after some uptime under the workflow engine; script pings a heartbeat job's last run timestamp.
soonScheduled jobs run sporadically on Redis backed deploys
Jobs run inconsistently on some hosting setups, leaving failed job records in Redis; script inspects the queue for stuck or failed entries.
soonEvents & Notifications
3 guidesRedis event bus intermittently drops or delays subscriber execution
Events like order.placed are not consistently delivered to subscribers, silently skipping notifications; script diffs expected vs fired events over a window.
soonEvents fire before subscribers finish loading on boot
Events emitted immediately at startup race subscriber registration, causing missed handlers right after deploy; script checks for gaps right after a restart.
soonA leftover workaround subscriber duplicates order confirmation emails
An expired custom workaround keeps re-firing order.placed after the root bug was patched, sending duplicate emails per order; script counts notifications per order.
soonModule Links & Migrations
3 guidesRenaming a linked module or model orphans existing link rows
Renaming a linked data model prompts dropping the old link table unless a third defineLink parameter is passed, orphaning records; script counts link rows vs referenced entities.
soonNo API to hard delete link table rows after entity removal
Deleting a linked entity leaves its row in the auto generated link table with no public delete method; script must call the dismiss step or raw query to clean up.
soonCustom module links do not cascade delete with the linked product
Deleting a product leaves dangling rows in custom module link tables pointing at a nonexistent product id; script scans links for missing product ids.
soonCatalog Import
4 guidesProduct import batch job hangs at preprocessing with no event
CSV product import jobs stall indefinitely in preprocessing status with no completion event fired; script polls batch jobs for ones stuck past a timeout.
soonCSV import ignores the variant inventory quantity column
Importing products via CSV leaves every imported variant at zero stock regardless of the source quantity column; script compares CSV quantities to resulting levels.
soonDuplicating a product scrambles variant option pairings
Duplicating a product mismatches variant and option value combinations versus the source; script diffs option sets between original and duplicate.
soonDuplicating a product fails on a unique variant barcode constraint
Product duplication throws a database unique index violation on variant EAN or barcode; script scans variants for duplicate barcode values across products.
soonFighting a Medusa bug right now?
If you have a problem in Medusa storefront access, pricing, inventory, orders, promotions, or workflows that you would rather hand off, this is the kind of work I do. Message me and we can work through it together.
Found this useful?
These guides are free and always will be. If one saved you a support ticket or a refund, you can throw a coffee my way. It keeps the notes coming.
Buy me a coffee on Ko-fi