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.

Python and Node.js Medusa Admin API Diagrams and full code All code on GitHub

Storefront and API access

3 guides

Regions, pricing, and currency

4 guides

Inventory and stock

5 guides

Cart and checkout

4 guides

Orders and fulfillment

4 guides

Promotions and campaigns

3 guides

Modules, links, and data

5 guides

Events, workflows, and jobs

4 guides

Migration and housekeeping

2 guides

Customer & Auth

4 guides

Guest 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.

soon

Orders 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.

soon

Admin 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.

soon

Failed 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.

soon

Inventory & Reservations

7 guides

Fulfillment 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.

soon

Multi-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.

soon

Existing 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.

soon

Reservation 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.

soon

Reservation 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.

soon

manage_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.

soon

Reserve 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.

soon

Stock Locations & Sales Channels

3 guides

Inventory 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.

soon

Checkout 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.

soon

Publishable 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.

soon

Payments & Refunds

8 guides

Custom 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.

soon

Outstanding 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.

soon

Custom 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.

soon

Refund 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.

soon

Only 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.

soon

Sub-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.

soon

New 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.

soon

Stripe 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.

soon

Order Edits & Totals

5 guides

Order 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.

soon

Editing 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.

soon

Draft 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.

soon

Order 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.

soon

Order 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.

soon

Fulfillment & Returns

3 guides

Fulfillment 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.

soon

Cannot 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.

soon

Fulfillment 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.

soon

Pricing & Promotions

9 guides

Buy 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.

soon

Campaign 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.

soon

Promotion 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.

soon

Shipping 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.

soon

Draft 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.

soon

A 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.

soon

Price 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.

soon

Multiple 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.

soon

Region 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.

soon

Workflows & Background Jobs

5 guides

Long 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.

soon

continueOnPermanentFailure 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.

soon

Scheduled 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.

soon

Scheduled 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.

soon

Scheduled 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.

soon

Events & Notifications

3 guides

Redis 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.

soon

Events 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.

soon

A 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.

soon

Module Links & Migrations

3 guides

Renaming 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.

soon

No 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.

soon

Custom 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.

soon

Catalog Import

4 guides

Product 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.

soon

CSV 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.

soon

Duplicating 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.

soon

Duplicating 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.

soon

Fighting 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.

Contact me on LinkedIn

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