Inventory tracking
Assemblified tracks inventory in four places, depending on what kind of stock it is:
- Shopify Admin API. Source of truth for Shopify-linked raw materials. Read on demand; written through Shopify’s inventory API.
- Virtual inventory levels (Assemblified-side). Per-location stock for virtual materialsVirtual MaterialA material tracked entirely inside Assemblified — not a Shopify variant. Useful for shop-floor consumables (glue, packaging, labour units) where you need quantity tracking but don't want a Shopify product on your storefront. Read more → .
- Pre-assembled inventory levels (Assemblified-side). Per-location stock for BOM and sub-assembly pre-built shelves.
- Residual inventory levels (Assemblified-side). Fractional rounding leftovers from work-order picks. Doesn’t affect BOM execution.
This page explains where each kind of stock lives and how they fit together.
On this page
Section titled “On this page”- The four storage tables
- Multi-location semantics
- The external-change recompute
- Inventory write paths
Storage by stock type
Section titled “Storage by stock type”| Stock type | Where it lives | Source of truth |
|---|---|---|
| Shopify-linked raw materials | Shopify Admin API | Shopify |
| Virtual materials | virtual_inventory_levels (Assemblified) | Assemblified |
| BOM pre-assembled | preassembled_inventory_levels (Assemblified) | Assemblified |
| Sub-assembly pre-assembled | preassembled_inventory_levels (synthetic IDs) | Assemblified |
| Work-order residual rounding | residual_inventory_levels (Assemblified) | Assemblified |
Each table is always per-location. The composite primary key includes locationId, so adjustments at one location don’t affect others.
Multi-location semantics
Section titled “Multi-location semantics”For Shopify-linked materials, multi-location follows Shopify’s model directly. Each Shopify location has its own inventory level (read via Admin API per location). Assemblified writes deltas per location.
For virtual materials, the same per-location structure exists in virtual_inventory_levels. Each location has its own row.
When a BOM with a multi-location component executes, Assemblified resolves the location for each component reference in this priority:
- Component reference’s
locationId. If set on the BOM/SA reference row, this material is always pulled from that location. - Order’s fulfillment location. Used when the shop has Location-sensitive dynamic adjustments on.
- Shop’s default location. Fallback.
External-change recompute
Section titled “External-change recompute”When inventory changes outside Assemblified — e.g., an operator adjusts stock manually in the Shopify admin, or another integration syncs in — Shopify fires the INVENTORY_ITEMS_UPDATE webhook. Assemblified:
- Receives the webhook.
- Updates its cached
inventoryQuantityfield on the affected raw material. - Triggers a dynamic adjustmentDynamic adjustmentA per-BOM toggle that recalculates the BOM's Shopify-displayed quantity from current component availability after every order. The displayed quantity is the bottleneck-resource count plus pre-assembled stock — your storefront never sells more than you can build. Read more → cascade for every BOM that uses this material — the BOMs’ displayed Shopify quantities are recomputed.
This keeps Assemblified-side BOM displays in sync with external Shopify changes.
Virtual materials don’t fire INVENTORY_ITEMS_UPDATE. Since they don’t exist in Shopify, external changes can only happen via the Assemblified UI or API. If you adjust virtual stock outside the UI (e.g., via SQL or the test endpoint), trigger a recompute manually with Synchronize with Raw Materials on affected BOMs.
Inventory write paths
Section titled “Inventory write paths”Inventory is written from many code paths. The main ones:
| Path | What gets written |
|---|---|
| BOM execution (create) | Shopify inventory adjustment for Shopify-linked components; internal update for virtual; pre-assembled shelf drawdown. |
| BOM execution (cancel/refund) | Inverse, respecting the cascade rule. |
| Pre-assembled manual adjust | preassembled_inventory_levels + the per-BOM/SA aggregate column. |
| Manual raw-material adjust | Shopify Admin API (Shopify-linked) or virtual_inventory_levels (virtual); plus the cached inventoryQuantity and the adjustment-history row. |
| Work Order pick | Inventory deltas via the build-run dispatchers; residual rounding writes residual_inventory_levels. |
| External Shopify change | Cached inventoryQuantity updated; dynamic-adjustment cascade triggered. |
Common gotchas
Section titled “Common gotchas”- Cached
inventoryQuantityis stale. Trust per-location tables. The aggregate field is for display only. - Negative inventory differs by stock type. Shopify-linked materials respect Shopify’s negative-inventory policy. Virtual materials and pre-assembled shelves are clamped at zero by Assemblified.
- Residual rounding only matters for work orders. BOM execution doesn’t round consumption — fractional quantities propagate exactly.
- Multi-location BOMs. A BOM with components at different locations executes at multiple locations in the same order. The InventoryAdjustQuantities mutation supports this; deltas are batched.
- Failed Shopify writes don’t roll back Assemblified’s state. Pre-assembled drawdown is committed before the Shopify call. If Shopify fails, Assemblified’s state has already moved. Recovery is via Synchronize or manual reconcile.
Where to next
Section titled “Where to next”- Shopify-linked materials — Shopify-side details.
- Virtual materials — internal-inventory details.