← Back to blog

Magento 2 Salable Quantity Wrong? How Reservations Work

Rodrigo Biazzi

"The product has 10 in stock but the storefront says out of stock." With MSI, that sentence usually means the salable quantity is lower than the quantity, and the difference is reservations. This article explains how reservations work in Magento Open Source and Adobe Commerce 2.4.8, why they drift, and how to fix them without editing the database by hand.

If sources and stocks are new to you, read MSI sources and stocks first. Reservations are attached to stocks, not to sources.

Quantity versus salable quantity

In the admin product grid you see two columns. Quantity per Source is what physically sits in each source, according to the source items. Salable Quantity is what each stock can still sell. They are calculated differently:

  • Start from the sum of quantities of the in stock source items in the sources linked to the stock.
  • Subtract the out of stock threshold, configured globally or per product.
  • Add the sum of all reservations for that SKU and stock. Reservations for open orders are negative, so this lowers the number.

When salable quantity reaches zero, the product is not salable on the websites using that stock, even if the source still shows quantity on hand.

What a reservation is

A reservation is a row in inventory_reservation with a stock ID, a SKU, a quantity and a JSON metadata field that says which event created it and for which object, usually an order. Reservations are append only. Magento never updates a reservation. It adds a new one with the opposite sign.

The lifecycle for a normal order looks like this:

  1. Order placed. A reservation of minus the ordered quantity is added. Salable quantity drops. Source quantity is unchanged.
  2. Order shipped. The shipped quantity is deducted from the chosen source items, and a reservation of plus the shipped quantity is added. The sum of reservations for that order is now zero, and the drop has moved from reservations to the source.
  3. Order canceled. A reservation of plus the canceled quantity is added. Salable quantity returns.
  4. Credit memo with return to stock. Quantity is added back to the source.

Once the reservations for an order add up to zero, they serve no purpose. A cron job removes reservation rows whose sum per SKU and stock is zero, so the table does not grow forever.

Why reservations drift

Reservations are only correct if every order goes through the full lifecycle inside Magento. In practice it breaks in a few predictable ways:

  • Orders completed without a shipment. An order marked complete or closed by code or by a module, without creating a shipment, never gets its compensating reservation. The stock stays reserved forever.
  • Orders deleted from the database. Deleting test orders with SQL leaves their reservations behind.
  • Stock set by an ERP that already accounts for open orders. If the ERP sends quantity already net of open orders, and Magento also subtracts reservations, the open orders are counted twice.
  • Shipments created in an external system. If the ERP ships and only updates the order status in Magento, the deduction and the compensation do not happen in Magento.
  • Consumers not running. Some inventory updates go through message queue consumers. If they do not run, reservation updates from mass actions can be delayed or lost.

Finding inconsistencies

Magento ships a command that compares reservations with orders and lists the mismatches:

bin/magento inventory:reservation:list-inconsistencies

It reports orders whose reservations do not add up to what the order state says. Useful options:

  • -c or --complete-orders lists only orders in a final state, complete, closed or canceled, which should have no open reservation.
  • -i or --incomplete-orders lists only orders still in progress.
  • -r or --raw prints the result in a compact format meant to be piped into the next command.
  • -b sets the bunch size for large order tables.

To see the totals directly in the database, this read only query lists SKUs with open reservations per stock:

SELECT stock_id, sku, SUM(quantity) AS reserved
FROM inventory_reservation
GROUP BY stock_id, sku
HAVING SUM(quantity) <> 0
ORDER BY reserved ASC
LIMIT 50;

A large negative number on a SKU that has no open orders is a clear sign of drift.

Fixing them with compensations

The fix is to add the compensating reservations that should have been created. Magento has a command for that, and it reads the raw output of the first one:

bin/magento inventory:reservation:list-inconsistencies -r \
  | bin/magento inventory:reservation:create-compensations

I usually run it for complete orders first, because those are unambiguous. An order that is complete or canceled should have zero reserved:

bin/magento inventory:reservation:list-inconsistencies -c -r \
  | bin/magento inventory:reservation:create-compensations

For incomplete orders, look before you compensate. An order in processing that will be shipped normally should keep its reservation. Compensating it makes the stock look higher than it is, and you risk overselling. The incomplete list is more useful for finding orders stuck in a state that nobody will move them out of.

Take a database backup first and run it on staging with a recent copy, as with any data correction. The command only adds rows to inventory_reservation, it does not change orders or sources. After running it, the cleanup cron removes the rows that now sum to zero.

Why not just truncate the table?

Truncating inventory_reservation removes the reservations of every open order too. All products ordered but not yet shipped become salable again at their full source quantity, which leads to overselling until those orders ship. It is only acceptable on a store with no open orders, which is rarely the case on a live store.

Preventing drift

  • Create shipments in Magento, even when fulfillment happens elsewhere. The ERP can call the ship order endpoint, POST /V1/order/{orderId}/ship, with the source code, instead of only setting the status. That way deduction and compensation happen in one place.
  • Agree on who owns available quantity. If the ERP sends physical stock, Magento subtracts open orders through reservations. If the ERP sends available stock, the two will double count. Pick one model and document it. The integration side is in ERP integration patterns.
  • Never delete orders with SQL. If test orders must go, cancel them first so their reservations are compensated.
  • Run cron and consumers and alert when they stop. See the cron jobs guide.
  • Schedule the check. Running the list command for complete orders once a week and alerting on any output catches problems early.

Reading salable quantity in code

When custom code needs to know whether a product can be sold, use the inventory sales API instead of reading quantities from tables. Magento\InventorySalesApi\Api\GetProductSalableQtyInterface returns the salable quantity for a SKU and stock ID, already including reservations and the threshold. Resolve the stock ID for the website with StockResolverInterface. Reading qty from the legacy stock item gives the source quantity of the default source, which is the wrong number for this question.

FAQ

Why is salable quantity lower than quantity in Magento 2?

Salable quantity subtracts reservations for orders that were placed but not yet shipped or canceled, plus the out of stock threshold. The difference is expected for open orders.

Is it safe to delete rows from inventory_reservation?

Not in general. Deleting rows of open orders makes their stock salable again and can cause overselling. Use the create compensations command, which adds balancing rows instead.

Why are reservations not cleared after an order is complete?

The compensating reservation is added when a shipment is created or the order is canceled. Orders closed without a shipment keep their negative reservation until you compensate it.

Does the cleanup cron fix wrong reservations?

No. It only removes rows that already sum to zero per SKU and stock. Wrong reservations have a non zero sum and stay until compensated.

I am available for freelance Magento and Adobe Commerce work, including inventory and order flow problems. If you need help with this, get in touch.

Related articles