Skip to main content

Stock states: on-hand, available, allocated, on-hold, non-sellable, incoming & in-transit

Every number you see on a product's Inventory tab — On Hand, Available, Allocated, On Hold, Non-Sellable, In Transit, Total — is derived, not stored. SKU.io keeps one source of truth (the movement ledger) and computes every stock state from it, along with the live allocation, hold, transfer, and bin records. This page explains what each state means, how they compose into each other, and the handful of subtleties — floored Available, over-commitment, non-sellable bins — that trip people up.

Read this once and the whole Inventory area makes sense: the list, the per-warehouse stock view, allocations, and holds all read the same derived columns.

Looking for exact field names and column labels?

This page explains the model. For the lookup table of every column, its API field, and where it shows, see the stock states & fields reference.

The one equation to remember

Physical stock at a warehouse splits into four buckets, and stock you own but haven't landed yet sits outside them:

On Hand = Available + Allocated + On Hold + Non-Sellable

Total Inventory = On Hand + In Transit

Read the first line left to right: everything physically sitting in a warehouse is either free to sell (Available), committed to an open sales order (Allocated), set aside by a manual hold (On Hold), or parked in a non-sellable bin (Non-Sellable). Add stock in flight between your own warehouses (In Transit) and you get Total Inventory — everything you own for that product at that location.

Rearranged, On Hand = Total − In Transit. The two equations reconcile: On Hand is the physical count on the shelf, Total adds what's still on the truck.

Incoming (stock still on a purchase order) sits outside the equation entirely — you don't own it at the warehouse yet. More on that below.

The four On Hand buckets

Each bucket answers a different question. All four are physically present in the warehouse right now — the difference is what's claiming them.

BucketWhat it holdsWhere the claim comes from
AvailableFree stock, sellable and unclaimed.Whatever's left after the other three buckets are subtracted.
AllocatedStock committed to unfulfilled sales orders.Allocations with status Allocated — the only allocation status that claims on-hand stock.
On HoldStock manually set aside for a non-order reason (QC, quarantine, investigation).Active stock holds. Removing the hold returns the units to Available.
Non-SellableStock physically here but sitting in a bin flagged not-sellable.Units in warehouse bins whose location is marked non-sellable.

Available is the residual. SKU.io computes it as:

Available = On-hand physical stock − Allocated − On Hold − Non-Sellable

Allocated, On Hold, and Non-Sellable each reduce Available while staying part of On Hand. So moving a unit into a damaged bin, placing it on hold, or allocating it to an order all shrink what you can sell without changing your physical count.

Only Allocated allocations claim stock

An allocation has five statuses, but only Allocated reduces on-hand Available. Planned and Awaiting Receipt allocations are demand awaiting supply — backorders — and never touch your physical buckets. See Allocations and backorders for the full state machine.

Available vs Available to Sell

There are two "available" numbers, and confusing them is the most common stock-state mistake.

NumberFloored at zero?Answers
AvailableYes — never negative"How much can I sell right now from stock on the shelf?"
Available to SellNo — goes negative"Am I over-committed once backorders are counted?"

Available is deliberately floored at zero on write. The cache stores GREATEST(0, on-hand − Allocated − On Hold − Non-Sellable), so it can never show a negative shelf count and the integrity checks stay consistent.

Available to Sell is recomputed live from the raw columns so it can go negative:

Available to Sell = (Total − In Transit) − Allocated − On Hold − Backorder Planned − Backorder (PO-covered)

When your commitments — allocations, holds, and backordered demand — exceed the stock actually on hand, Available stays pinned at 0 while Available to Sell drops below zero. That negative number is the signal that you've oversold: you've promised more than you hold. On the Inventory tab and totals row, a negative Available to Sell renders in red so it can't be missed.

Available floored, Available to Sell negative = oversold

If Available reads 0 but Available to Sell reads -15, you have 15 units of demand you can't cover from current stock. The floor on Available keeps the shelf count honest; Available to Sell is where the over-commitment surfaces. Work it down through the backorder queue or by receiving stock.

Non-Sellable bins are first-class

Stock can be physically in your warehouse yet unsellable — sitting in a quarantine, damaged, or hold bin whose location is flagged non-sellable. SKU.io surfaces that quantity as its own Non-Sellable column on the Inventory tab.

This matters for the On Hand equation. Non-Sellable units:

  • reduce Available (you can't sell them), and
  • stay counted in On Hand and Total Inventory (they're physically here).

Because Non-Sellable is a first-class component of On Hand — On Hand = Available + Allocated + On Hold + Non-Sellable — physically present-but-unsellable stock is never silently dropped from your On Hand count. It shows in its own column (highlighted when non-zero) and still reconciles with Total − In Transit.

To move units in or out of a non-sellable bin, use a location transfer; the Non-Sellable and Available columns update on the next cache refresh.

Where does non-sellable stock come from?

A bin is non-sellable when its warehouse location is flagged that way (for example, a "Damaged" or "Quarantine" bin). Any stock you move into that bin counts toward Non-Sellable. See Move stock between bins.

Incoming vs In Transit: Two kinds of pending stock

Two columns describe stock that isn't fully on your shelf. They're not interchangeable.

ColumnMeansDo you own it yet?Counts in Total?
IncomingUnreceived quantity on open purchase orders — stock you've ordered from a supplier but not received.NoNo
In TransitStock moving between your own warehouses (a warehouse transfer that's shipped but not received, or Amazon AWD).YesYes

Incoming (also labeled Inbound) counts only the unreceived portion of purchase order lines on open POs. It's attributed to the destination warehouse of the specific inbound shipment carrying it; any PO-line remainder not yet on a shipment falls back to the PO header's destination warehouse. Terminal shipments (received, closed, cancelled) are excluded — they've already released their expected quantity, so counting them would double-count phantom supply.

In Transit is your own stock in flight from one of your warehouses (or an Amazon AWD facility) to another. It's already yours, so it's part of Total Inventory — it just hasn't landed, which is why it's excluded from On Hand.

Rule of thumb

Incoming = a supplier still owes it to you (a purchase order is open). In Transit = you already own it and it's moving between your own locations (a transfer is shipped, not received).

Which product types carry inventory

Not every product holds stock of its own. Only four of the six product types do:

TypeCarries inventory?Notes
StandardYesThe ordinary stocked product.
KitYesStocked in its own right (distinct from a bundle).
BlemishedYesDerived from a source product as a graded/second-quality variant.
ManufacturedYesProduced in-house; carries its own stock.
BundleNoSellable, but stock is derived from its components — a bundle has no ledger of its own.
MatrixNoA parent that groups variations; the stocked units are its child variations, not the parent.

For types that carry inventory, the product detail page shows the full set of stock tabs — Inventory, Movements, Allocations, FIFO Layers, Lots & Expiry, COGS, and Holds. Bundle and Matrix parents hide those tabs because they hold no direct stock.

Matrix parents vs variations

A Matrix product is a parent with variations; each variation is a separate stocked product (its parent link is set). The parent aggregates its children but has no inventory ledger itself.

How the numbers are computed: The derived cache

The Inventory tab reads a cache table (products_inventory), one row per product-and-warehouse plus a rollup "all warehouses" row. That cache is a derived summary — the real source of truth is the movement ledger together with the live allocation, hold, FIFO-layer, transfer, and bin records.

SKU.io rebuilds a product's cache row by aggregating all of those sources in one pass:

  • On-hand physical stock and In Transit come from the movement ledger (active vs in_transit movements), plus Amazon AWD transfers for In Transit.
  • Allocated / Backorder Planned / Backorder Awaiting-Receipt come from the allocation records, one per status.
  • On Hold sums the active stock holds.
  • Non-Sellable sums stock in non-sellable bins.
  • Stock value and average cost come from open, non-consignment FIFO layers.

The rebuild runs two ways: a full rebuild for a set of products, and an incremental delta applied the instant a movement is saved or deleted. Allocation changes get an even lighter touch — a surgical refresh of just the allocation-derived columns rather than a full recompute. Every rebuild is deadlock-hardened (canonical ordering, stale-row sweep, automatic retry) and, when it finishes, emits an event so any live view or export refreshes.

The cache can briefly lag — a saved/deleted observer is the guaranteed refresh

Because the numbers are cached, a value can momentarily trail the ledger under heavy concurrency. The one guaranteed trigger is the movement (and allocation/hold) save/delete observer, which re-derives the affected rows. If a number ever looks stale, re-opening the product forces a fresh read; the movement ledger is always authoritative.

The cache table vs the realtime view

Two structures expose these numbers, for two different purposes:

StructureDefinitionUsed by
products_inventory (cache table)Per-product + per-warehouse cache, with a warehouse 0 "all warehouses" total row.The product Inventory tab, the inventory list, and cheap existence checks.
view_realtime_inventory (database view)A product + warehouse rollup that also exposes Incoming and a projected total.The Realtime Inventory report and its exports.

A lightweight "does this product have any stock at all?" check reads the cache table directly (it powers the setup guide's "opening inventory entered" signal) rather than the heavier realtime view.

A note on terminology: "On Hold", not "Reserved"

Stock set aside by a manual hold is labeled On Hold everywhere in the UI — both the product Inventory tab and the product inventory list column. The list column used to be labeled Reserved; it was renamed to On Hold, so the two views now agree.

The only place the older name survives is a deprecated inventory_reserved field in the API, kept as a backward-compatibility alias for On Hold and slated for cleanup.

There is no "Reserved" stock status

True reserved inventory movements were removed from SKU.io. There is no "reserved" inventory status, and the old reserved-quantity relationship always returns zero. Stock committed to orders is Allocated; stock set aside manually is On Hold. Neither is called "reserved."

How holds check availability

When you place a stock hold, SKU.io checks that enough uncommitted stock exists before accepting it:

Hold availability = On Hand − Allocated − existing active holds

Note what this doesn't subtract: it doesn't net out Planned/backordered demand or In Transit stock. A hold competes only with order allocations and other holds for the physical on-hand pool. Request more than that and the hold is rejected with an "insufficient available inventory" message telling you the net-of-holds figure.

Putting it together: A worked reconciliation

Say a product at Main Warehouse shows:

ColumnValue
Available40
Allocated30
On Hold5
Non-Sellable10
On Hand85
In Transit15
Total Inventory100
Incoming200

On Hand is 40 + 30 + 5 + 10 = 85. Total is 85 + 15 = 100, and On Hand also equals Total − In Transit = 100 − 15 = 85. The 200 Incoming sits outside both — it's still on an open purchase order. If demand climbed so that Allocated plus backorders exceeded the 85 on hand, Available would floor at 0 and Available to Sell would go red and negative, flagging the oversell.

Next steps

Last verified: