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.
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.
| Bucket | What it holds | Where the claim comes from |
|---|---|---|
| Available | Free stock, sellable and unclaimed. | Whatever's left after the other three buckets are subtracted. |
| Allocated | Stock committed to unfulfilled sales orders. | Allocations with status Allocated — the only allocation status that claims on-hand stock. |
| On Hold | Stock manually set aside for a non-order reason (QC, quarantine, investigation). | Active stock holds. Removing the hold returns the units to Available. |
| Non-Sellable | Stock 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.
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.
| Number | Floored at zero? | Answers |
|---|---|---|
| Available | Yes — never negative | "How much can I sell right now from stock on the shelf?" |
| Available to Sell | No — 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.
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.
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.
| Column | Means | Do you own it yet? | Counts in Total? |
|---|---|---|---|
| Incoming | Unreceived quantity on open purchase orders — stock you've ordered from a supplier but not received. | No | No |
| In Transit | Stock moving between your own warehouses (a warehouse transfer that's shipped but not received, or Amazon AWD). | Yes | Yes |
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.
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:
| Type | Carries inventory? | Notes |
|---|---|---|
| Standard | Yes | The ordinary stocked product. |
| Kit | Yes | Stocked in its own right (distinct from a bundle). |
| Blemished | Yes | Derived from a source product as a graded/second-quality variant. |
| Manufactured | Yes | Produced in-house; carries its own stock. |
| Bundle | No | Sellable, but stock is derived from its components — a bundle has no ledger of its own. |
| Matrix | No | A 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.
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 (
activevsin_transitmovements), 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.
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:
| Structure | Definition | Used 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.
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:
| Column | Value |
|---|---|
| Available | 40 |
| Allocated | 30 |
| On Hold | 5 |
| Non-Sellable | 10 |
| On Hand | 85 |
| In Transit | 15 |
| Total Inventory | 100 |
| Incoming | 200 |
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
- View a product's stock across warehouses — see these columns per warehouse.
- Browse the product inventory list — the tenant-wide view of every product's stock columns.
- Allocations and backorders — how the Allocated bucket and backorder demand work.
- Place and manage stock holds — how the On Hold bucket is filled.
- The inventory movement ledger — the source of truth every number is derived from.
- Stock states & fields reference — every column, field, and label in one table.