Skip to content

Order reservations

When a paid order routes to a location in a multi-location store, Shopify reserves the finished-good variant at that location — but the BOM components it would consume are still sitting in available. Until the order is actually fulfilled, another order can sell those same components out from under it. Order reservations close that window: at routing-complete, Assemblified moves the resolved component quantities from available to reserved at the routed location, and consumes them on fulfillment.

This is an opt-in shop-wide setting. Without it, components stay available until fulfillment and are deducted then — the deduct-at-fulfillment behaviour.

  • What it does and why
  • Prerequisites and how to enable
  • The five Shopify webhooks that drive it
  • Lifecycle states
  • How each material kind behaves (Shopify, virtual, pre-assembled)
  • Failure handling and retry
  • Manual cleanup of remaining reservations
  • Reservation backfill for orders that predate the setting
  • How it differs from safety-stock reservations
  • Edge cases & gotchas

For every BOMBill of MaterialsA bill of materials tells Assemblified how to build one unit of a finished good. When a customer orders the finished-good variant, Assemblified deducts the right component quantities from inventory automatically. Read more → line item on an incoming order, Assemblified expands the recipe (recursing through assembly bills, applying waste percentages) to the flat list of raw materials and pre-assembled draw-down. When Shopify completes routing for the order’s fulfillment orderFulfillment OrderShopify's unit of fulfillment work: when a customer order is routed, Shopify splits it into one fulfillment order per fulfilling location, each carrying the line items that location will ship. Assemblified listens to the fulfillment-order lifecycle events (routing complete, hold, release, move, cancel) to drive per-location features like order reservations. Read more → , those resolved components are reserved at the routed location:

  • Shopify-linked materials — available drops and reserved rises by the same amount, in Shopify’s own inventory buckets.
  • Virtual materials — Assemblified’s own available figure drops; a history entry records the reservation.
  • Pre-assembled stock — when a BOM component is itself a finished-good with a pre-assembled shelf, the shelf is drawn down first, then any remainder draws from raws.

When the order is fulfilled, the reserved quantities are consumed — reserved drops, the shelf doesn’t bounce back to available, and the inventory is gone for good. If the order is cancelled, refunded, or rerouted before fulfillment, the reservation is released — quantities return to available. The one exception is a refund or cancellation where you choose Don’t restock: then the refunded units are consumed right away instead of released, because you told Shopify they are gone (see Refunds & cancellations → “Don’t restock”).

The default multi-location behaviour deducts components at fulfillment, which means between order-paid and shipment the storefront still shows those components as sellable. In a fast-moving store this opens an over-promise window: a flurry of orders for two BOMs that share a bottleneck component can all succeed at checkout, then collide at fulfillment.

Turning on order reservations trades visible storefront stock against safety. The tradeoff:

With reservationsWithout reservations
Component available drops at routing-complete (typically right after payment).Component available drops only at fulfillment.
Storefront reflects the true buildable count immediately.Storefront may temporarily over-promise during the routing→fulfillment window.
Concurrent orders for shared components contend at routing, not at fulfillment.Contention surfaces at fulfillment time, after both orders are paid.

Use it when over-promising during the routing→fulfillment window is a real problem — high order velocity, components shared across multiple BOMs, fulfillment lag that’s measured in days rather than minutes.

The switch lives at Settings → General, in the section headed Multi-location sensitive adjustments, and it reads “Track BOM materials as Shopify-reserved between routing and fulfillment.”

  1. Turn on “Respect order and fulfillment locations when available” first. It is the parent switch of that section; everything below it stays disabled until it is on.
  2. Switch “Track BOM materials as Shopify-reserved…” on. The section spells out the tradeoff underneath: visible Shopify storefront stock drops at routing time rather than at fulfillment.
  3. New orders from this point forward route through the reservation flow. In-flight orders that were already routed before the change keep the deduct-at-fulfillment behaviour.

With the switch off, the same section describes what you get instead: “Deduct at fulfillment: routing records a per-fulfillment-order intent with no stock movement, and each fulfillment deducts available stock at its own location, so split orders and partial shipments reduce at the correct location.”

Five Shopify fulfillment-order lifecycle webhooks drive the flow. They are registered automatically on install — no manual setup.

WebhookWhat it triggers
fulfillment_orders/order_routing_completeCommit the reservation at the assigned location — or hold it, if the fulfillment order arrived already on hold.
fulfillment_orders/placed_on_holdRelease the reservation back to available, and mark it held so the next event can re-activate it.
fulfillment_orders/hold_releasedCommit again — re-reserves the same components at the same location.
fulfillment_orders/movedRelease at the source location and commit at the destination, atomically. If the destination commit fails, the source release is rolled back so no stock is permanently lost.
fulfillment_orders/cancelledRelease, recording the fulfillment-order cancellation as the reason.

These supplement the orders/updated webhook, which still handles order-edit and refund paths, and the fulfillments/* topics that report the shipment itself.

A reservation moves through:

  • Active — committed, holding stock at the routed location.
  • Held — was active until the fulfillment order went on hold; the stock has been returned to available and is waiting for the hold to be released.
  • Consumed (or partially consumed) — fulfillment, or a partial fulfillment, consumed the reserved quantities.
  • Released — cancelled, refunded or rerouted; stock returned to available.
  • Failed — the commit was attempted but the Shopify write failed for every line. The local record is kept so it can be retried.

Every transition is recorded: created, held, hold released, consumed, released, rerouted out, rerouted in, Shopify synced, Shopify sync failed, Shopify reverted, failed.

Material kindOn commitOn releaseOn consume
Shopify-linkedShopify available → reserved at the routed location.reserved → available at the same location.reserved decrements; the inventory is permanently consumed.
Virtual (Assemblified-only)Assemblified’s own available figure decrements; a history entry is written. No Shopify call.Available increments; a history entry is written.A history entry is written; the stock was already deducted at commit.
Pre-assembled draw-downThe BOM’s pre-assembled shelf decrements at the routed location before any raw-material commit happens.Pre-assembled shelf increments back.Recorded only; the shelf decrement at commit is final.

A single reservation carries lines of all three kinds when the BOM mixes them — the reservation is the atomic unit, not the line.

Shopify writes can fail (rate-limited, session expired, in-flight network drop). When the Shopify push for a commit fails:

  • The local reservation is still recorded — what you asked for is preserved.
  • The affected lines are marked as failed to sync.
  • A “Retry Shopify sync” action replays the failed lines without re-pushing successful ones.

Retrying the sync is the right path; re-editing the reservation also fixes it but is the long way around.

To review a material’s remaining order reservations, tick it on the Raw materials list and choose Inventory → Reserved quantities in the bar along the bottom. (That entry only appears while multi-location sensitive adjustments are on.)

The dialog lists one row per order and location, with what is Reserved, how much is Already used, the row’s Age and its Status — including badges for a hold that has not reached Shopify, a sync that failed, a partially used row and a row Shopify will not change. Narrow it with the Location filter, then answer “What should happen to the selected rows?”:

  • Return to stock — move the reserved units back to available.
  • Write off — take them out of inventory. The dialog warns before you do: writing off lowers on-hand stock.

Download changes takes the planned movements away for checking, before and after the run. Review them before applying.

You can clean up different materials from the same reservation in separate operations. A repeated request for an already completed operation returns the recorded result without moving stock again. If an attempt fails or reports an uncertain result, compare the reserved quantity in Shopify with the dialog’s planned changes before starting another attempt — the dialog prints both.

Reservation backfill: orders that never got a reservation

Section titled “Reservation backfill: orders that never got a reservation”

Switching the setting on does not reach backwards. Orders processed before it was on are still waiting for fulfillment with no reservation behind them, so their components are still sitting in available. Settings → Tools → Reservation backfill is the three-stage tool that fixes that, and it is gated on the same setting — with multi-location sensitive adjustments off, the page explains itself and every control is disabled.

  1. Scan. Candidate orders verifies each waiting order live against Shopify — still unfulfilled, not cancelled — and returns a table with a Verdict per row, summarised as “N eligible · N need review · N skipped”. Eligible rows are tickable; a partially fulfilled order needs review and has to be opted in by ticking its own Include box; skipped rows carry no checkbox at all. A row already partly covered says so — “N of M fulfillment orders already reserved”.
  2. Preview. Preview reservations (N) produces a read-only dry run through the same resolver the live path uses: Aggregated material movements (material, movement, quantity, location) and a Per-order detail table underneath, down to dropped lines and skipped inactive bills. Nothing has moved yet. Archive it as PDF, CSV or XLSX — this is the record of what you are about to do.
  3. Execute. Execute backfill (N order(s)) asks for confirmation, then queues the work and polls until the executed orders have left the waiting state. If anything changed between the scan and the execute, a warning says so before you continue.

There is also a Sheet filter: edit the Detail sheet you downloaded in stage 2, drop it back in (XLSX or CSV), and the run is narrowed to exactly the materials that survived your edit. The page reports what the sheet kept out of the total, names any rows it could not match, and names any order in the sheet that is in progress and therefore still needs its Include box ticked.

The steps above cover orders Assemblified recorded as waiting for fulfillment. Orders that were already open when you activated their bills — or when you installed Assemblified — were never handled at all: activating a bill only affects orders Shopify sends from then on. The same page has a second source for them, Open Shopify orders, which finds them in Shopify, reserves only the units still to ship, and asks what to do with any order Assemblified already deducted materials for. After that they behave like every other reserved order. See Bring open orders under Assemblified after a stocktake.

Order reservations vs safety-stock reservations

Section titled “Order reservations vs safety-stock reservations”

The two features share the word “reservation” but model different things. Don’t confuse them.

Safety-stock reservationsOrder reservations
Triggered byOperator action (“hold N units’ worth of components”)Shopify fulfillment-order lifecycle (automatic)
OwnerA finished good or an assembly billAn order + fulfillment-order + location
LifespanIndefinite — operator releases itBounded — released or consumed when the FO closes
Shopify bucketSafety stockReserved
Where it is setThe Reservations tab on the billOne shop-wide switch

They can coexist. A BOM can have an active safety-stock reservation holding 10 units’ worth of components and still create order reservations for incoming orders against the same components — the safety stock has already been moved out of available, so the orders just see the remaining pool.

See Safety-stock reservations for the operator-driven variant.

  • Single-location stores are a no-op. Everything here depends on multi-location sensitive adjustments being on; with it off, all five webhooks short-circuit and the reservations switch is disabled.
  • Switching it off doesn’t strand in-flight reservations. Existing reservations stay as they are and still consume on fulfillment or release on cancel. Incoming events simply stop creating new ones.
  • “Held” is a state, not a hold. Held is what a reservation becomes after it was placed on hold and its stock was already returned to available; it waits there for the hold to be released. A fulfillment order that arrives at routing time already on hold never commits in the first place.
  • Reroute is atomic. A move releases at the source and commits at the destination as a unit. If the destination commit fails, the source release is rolled back; you never end up with two active reservations for the same scope.
  • Late-arrival guard. If a fulfillment event arrives before its routing-complete (rare, but possible under retries), the consume step does nothing rather than over-decrementing, and the deduct-at-fulfillment path handles the order normally.
  • Inactive bills are skipped. An inactive bill produces no reservation lines; its components stay available for the duration of that order.
  • The visible storefront drops at routing, not fulfillment. This is the whole point — but worth flagging for merchants used to the legacy timing. Marketing campaigns timed around shipment dates will see the stock disappear earlier than they may expect.