On-Hand vs Available: The Real Reason Your WMS Breaks Shopify Inventory
Your warehouse management system says it updated inventory. Shopify shows different numbers. Orders with backorders randomly show zero stock. Your preorders keep getting cancelled.
If this sounds familiar, you’re probably dealing with a WMS that’s still using Shopify’s deprecated REST API.
Here’s what’s actually happening - and more importantly, how to fix it.
The Symptoms: What You’re Seeing
Before diving into the technical weeds, let’s confirm you’re experiencing this specific problem:
- Random inventory zeroing: Products suddenly show 0 available despite warehouse confirming stock
- Backorder chaos: Preorders flip to 0 available overnight
- Location mismatches: Multi-location inventory never quite syncs right
- “Phantom” stock: Shopify shows available inventory that doesn’t physically exist
- Sync delays: Changes take forever to reflect, or require manual intervention
Sound familiar? You’re not alone. We’ve seen this pattern across dozens of WMS providers still clinging to REST.
The Real Problem: REST Can’t Handle Modern Inventory
Here’s the core issue: REST can only update available
inventory. It literally cannot set on_hand
(your physical stock count). Only GraphQL can do that. This single limitation breaks everything else.
Shopify’s inventory model has evolved. It now tracks inventory by state at each location:
on_hand (physical total) = available + committed + reserved + damaged + safety_stock + quality_control
Here’s what each means:
- on_hand: Total physical units at the location
- available: Units that can be sold right now
- committed: Units allocated to placed orders
- reserved: Units temporarily held (cart abandonment, draft orders)
- damaged: Unsellable inventory
- safety_stock: Buffer to prevent overselling
- quality_control: Units being inspected
What REST Can and Can’t Do
The REST API has a fatal limitation - it can only update the available
quantity. It literally cannot set on_hand
inventory.
REST Capabilities:
- ✅ Update available inventory (what can be sold)
- ✅ Adjust quantities up or down
- ✅ Basic location management
REST Limitations:
- ❌ Cannot set on_hand inventory (total physical stock)
- ❌ Cannot manage inventory states (damaged, reserved, etc.)
- ❌ No proper audit trails with reference URIs
- ❌ No scheduled inventory changes for incoming stock
- ❌ Limited to 100 variants per product (product model limitation, not inventory-specific)
This isn’t a bug. It’s architectural. The REST API was built for Shopify’s old inventory model and will never support the new one.
The Deprecation Timeline (Yes, It’s Real)
Key dates you need to know:
- October 1, 2024: REST Admin API officially marked as “legacy”
- February 1, 2025: Public apps using REST must migrate to GraphQL
- April 1, 2025: Custom apps using REST must migrate
After these deadlines, apps face delisting from the App Store, installation warnings, and eventual complete failure. Full migration guide here.
Your WMS might still be “working” because Shopify hasn’t fully disabled REST yet. But it’s in maintenance mode - no fixes, no improvements, and eventually, no function.
How to Fix It: The GraphQL Migration Path
The solution is migrating to Shopify’s GraphQL Admin API. Here’s exactly what needs to happen:
1. Use the Right Mutation for Physical Inventory
Instead of the REST endpoint that only handles available
, use GraphQL’s inventorySetQuantities
:
mutation SetPhysicalInventory {
inventorySetQuantities(input: {
name: "on_hand", # This is the key - setting physical count
reason: "correction",
referenceDocumentUri: "gid://your-wms/CycleCount/CC-2025-001",
quantities: [{
inventoryItemId: "gid://shopify/InventoryItem/123",
locationId: "gid://shopify/Location/456",
quantity: 150
}]
}) {
inventoryAdjustmentGroup {
changes {
name
delta
quantityAfterChange
}
}
userErrors {
message
code
}
}
}
Note: The
referenceDocumentUri
field provides complete audit trails for inventory adjustments - something REST never supported.
This sets the physical inventory and lets Shopify calculate what’s actually available to sell.
2. Handle Backorders and Preorders Properly
For products that can be backordered:
- Enable “Continue Selling When Out of Stock” (inventoryPolicy: CONTINUE)
- Set inventory states correctly using GraphQL
- Schedule incoming inventory for visibility (note: scheduled changes don’t auto-apply)
mutation EnableBackorders {
productVariantsBulkUpdate(
productId: "gid://shopify/Product/123",
variants: [{
id: "gid://shopify/ProductVariant/456",
inventoryPolicy: CONTINUE # Allow selling when out of stock
}]
) {
productVariants {
inventoryPolicy
}
userErrors {
message
field
}
}
}
Then schedule incoming inventory for planning visibility using inventorySetScheduledChanges
.
Important: Scheduled changes are for planning only - they don’t automatically update inventory on the expected date. You must call
inventorySetQuantities
orinventoryAdjustQuantities
on that date to actually move the inventory.
mutation ScheduleIncoming {
inventorySetScheduledChanges(input: {
reason: "movement_created",
referenceDocumentUri: "gid://your-wms/PO/12345",
items: [{
inventoryItemId: "gid://shopify/InventoryItem/123",
locationId: "gid://shopify/Location/456",
ledgerDocumentUri: "gid://your-wms/PO/12345",
scheduledChanges: [{
fromName: "incoming",
toName: "available",
expectedAt: "2025-02-15T00:00:00Z"
}]
}]
}) {
scheduledChanges {
expectedAt
fromName
toName
quantity
}
userErrors {
message
code
}
}
}
3. Manage Inventory States
Move inventory between states as needed using inventoryMoveQuantities
:
mutation MoveToDamaged {
inventoryMoveQuantities(input: {
reason: "damaged",
referenceDocumentUri: "gid://your-wms/Adjustment/ADJ-2025-001",
changes: [{
inventoryItemId: "gid://shopify/InventoryItem/123",
from: {
name: "available",
locationId: "gid://shopify/Location/456"
},
to: {
name: "damaged",
locationId: "gid://shopify/Location/456"
},
quantity: 5
}]
}) {
inventoryAdjustmentGroup {
changes {
name
delta
quantityAfterChange
}
}
userErrors {
message
field
}
}
}
The Minimal Workaround (While You Migrate)
If your WMS provider hasn’t migrated yet, here’s a stopgap solution:
- Continue using REST for available updates (for now)
- Add daily GraphQL sync for on_hand to correct the physical count
- Log discrepancies to identify problem SKUs
- Use Shopify admin for critical corrections
This hybrid approach keeps you running while pushing for a proper fix.
Copy-Paste Email for Your WMS Provider
Need to light a fire under your WMS provider? Here’s an email template:
Subject: Critical: Shopify REST API Deprecation Breaking Our Inventory Sync
Hi [WMS Support],
We’re experiencing inventory sync issues because your integration uses Shopify’s deprecated REST API, which cannot update “on_hand” inventory - only “available” quantities.
Shopify’s REST API was marked legacy on October 1, 2024. Apps using deprecated endpoints face delisting and functionality loss throughout 2025.
What we need:
- Migration to Shopify’s GraphQL Admin API
- Use
inventorySetQuantities
mutation to set on_hand inventory per location- Let Shopify derive “available” from (on_hand - committed - reserved)
- Support for inventory states (damaged, reserved, quality_control)
- Proper handling of backorders via inventoryPolicy: CONTINUE
- Scheduled changes for incoming inventory visibility (with manual application on expected date)
Documentation:
- Managing inventory quantities and states
- REST to GraphQL migration guide
- New product model migration
- GraphQL inventory mutations reference
- API versioning and deprecation practices
Please provide an ETA for GraphQL migration. We need this resolved before our inventory discrepancies impact more orders.
Thanks, [Your name]
Red Flags: Evaluating a WMS in 2025
Important: As of October 1, 2024, the REST Admin API is officially legacy. All new apps and integrations must use GraphQL.
Shopping for a new WMS? Here’s your checklist:
Must-Haves:
- ✅ Uses GraphQL Admin API (not REST)
- ✅ Supports on_hand inventory updates
- ✅ Handles multiple inventory states
- ✅ Provides audit trails with reference URIs
- ✅ Supports 2000+ variants (new GraphQL product model vs REST’s 100)
- ✅ Can schedule inventory changes for incoming stock visibility
Nice-to-Haves:
- Webhook subscriptions for real-time sync
- Bulk operations support
- Race condition handling with compareQuantity
- Proper error recovery and retry logic
The Bottom Line
If your WMS is still using REST, your inventory will never be accurate. REST cannot and will never support on_hand inventory. This isn’t a configuration issue or a sync timing problem - it’s a fundamental API limitation.
The good news? The GraphQL migration path is well-documented. The bad news? Your WMS provider needs to actually do it.
Start the conversation now. Send that email. Push for a timeline. Because Shopify isn’t going backwards, and neither should your inventory management.
Additional Resources
- Shopify’s inventory management app guide
- InventoryLevel object reference
- InventoryItem object reference
- REST Admin API inventory endpoints (deprecated, for comparison)
Having inventory sync issues with your 3PL or WMS? 3PL Pulse monitors these exact discrepancies - catching problems before they impact orders. But first, make sure your WMS is speaking the right language to Shopify.