Skip to content

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.

  • The four storage tables
  • Multi-location semantics
  • The external-change recompute
  • Inventory write paths
Stock typeWhere it livesSource of truth
Shopify-linked raw materialsShopify Admin APIShopify
Virtual materialsvirtual_inventory_levels (Assemblified)Assemblified
BOM pre-assembledpreassembled_inventory_levels (Assemblified)Assemblified
Sub-assembly pre-assembledpreassembled_inventory_levels (synthetic IDs)Assemblified
Work-order residual roundingresidual_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.

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:

  1. Component reference’s locationId. If set on the BOM/SA reference row, this material is always pulled from that location.
  2. Order’s fulfillment location. Used when the shop has Location-sensitive dynamic adjustments on.
  3. Shop’s default location. Fallback.

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:

  1. Receives the webhook.
  2. Updates its cached inventoryQuantity field on the affected raw material.
  3. 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 is written from many code paths. The main ones:

PathWhat 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 adjustpreassembled_inventory_levels + the per-BOM/SA aggregate column.
Manual raw-material adjustShopify Admin API (Shopify-linked) or virtual_inventory_levels (virtual); plus the cached inventoryQuantity and the adjustment-history row.
Work Order pickInventory deltas via the build-run dispatchers; residual rounding writes residual_inventory_levels.
External Shopify changeCached inventoryQuantity updated; dynamic-adjustment cascade triggered.
  • Cached inventoryQuantity is 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.