skills/catalog-inventory/multi-warehouse/SKILL.md
Manage inventory across multiple warehouses with smart allocation rules, transfer orders between locations, and split-fulfillment routing
npx skillsauth add finsilabs/awesome-ecommerce-skills multi-warehouseInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Multi-warehouse inventory lets you stock products at multiple physical locations — warehouses, stores, 3PLs — and route each order to the location best positioned to fulfill it. Shopify and BigCommerce have multi-location inventory built in. WooCommerce needs a plugin. Only build a custom allocation engine if your routing logic (zone-based, cost-optimized, split fulfillment) exceeds what platform tools support.
| Platform | Built-in Multi-Location | Recommended Extension | |----------|-----------------------|----------------------| | Shopify | Yes — up to 1,000 locations (Basic: 4, Shopify plan: 5, Advanced: 8) | ShipBob or Flexport for 3PL fulfillment routing; ShipHero for advanced routing rules | | WooCommerce | No — requires a plugin | ATUM Multi-Inventory (add-on) or WooCommerce Multi-Location Inventory by Iconic | | BigCommerce | Yes — Multi-Location Inventory app (free) | ShipStation for advanced routing; Whiplash or ShipBob for 3PL integration | | Custom / Headless | Build allocation engine with proximity-first routing | Required when no platform tools meet routing and split-fulfillment requirements |
Shopify has native multi-location inventory.
Set up locations:
Configure fulfillment routing:
Transfer orders between locations:
3PL integration:
WooCommerce requires a plugin for multi-location inventory.
Option A: ATUM Multi-Inventory (recommended)
Option B: WooCommerce Multi-Location Inventory by Iconic
Transfer orders:
BigCommerce supports multi-location inventory via a free app.
Enable Multi-Location Inventory:
Configure fulfillment routing:
For 3PL integration:
For headless storefronts, build an allocation engine that selects the best location(s) for each order:
// lib/allocation.ts
import { haversineDistance } from './geo';
interface OrderItem { variantId: string; quantity: number; productName: string; }
interface AllocationResult {
type: 'single' | 'split';
fulfillments: { locationId: string; items: OrderItem[] }[];
}
export async function allocateOrder(orderItems: OrderItem[], shippingAddress: Address): Promise<AllocationResult> {
const locations = await db.locations.findMany({ where: { active: true } });
// Fetch inventory availability for all required variants at all locations
const inventory = await db.inventoryLevels.findMany({
where: { variantId: { in: orderItems.map(i => i.variantId) } },
});
const invMap = buildInventoryMap(inventory); // locationId -> variantId -> available
// Score locations by distance to shipping address (proximity-first routing)
const scored = locations
.map(loc => ({
...loc,
distanceKm: haversineDistance({ lat: loc.lat, lng: loc.lng }, { lat: shippingAddress.lat, lng: shippingAddress.lng }),
}))
.sort((a, b) => a.distanceKm - b.distanceKm);
// Prefer single-location fulfillment to avoid split shipments
for (const location of scored) {
const canFulfillAll = orderItems.every(item => (invMap[location.id]?.[item.variantId] ?? 0) >= item.quantity);
if (canFulfillAll) return { type: 'single', fulfillments: [{ locationId: location.id, items: orderItems }] };
}
// Fall back to split fulfillment — greedy assignment to nearest location with stock
const remaining = [...orderItems];
const fulfillments: AllocationResult['fulfillments'] = [];
for (const location of scored) {
const canFulfill = remaining.filter(item => (invMap[location.id]?.[item.variantId] ?? 0) >= item.quantity);
if (canFulfill.length > 0) {
fulfillments.push({ locationId: location.id, items: canFulfill });
canFulfill.forEach(item => remaining.splice(remaining.findIndex(r => r.variantId === item.variantId), 1));
}
if (remaining.length === 0) break;
}
if (remaining.length > 0) throw new Error('Cannot fulfill order — insufficient stock across all locations');
return { type: 'split', fulfillments };
}
// Transfer order management
export async function receiveTransferOrder(transferOrderId: string, receivedItems: { variantId: string; quantity: number }[]) {
const transfer = await db.transferOrders.findUnique({ where: { id: transferOrderId }, include: { items: true } });
await db.$transaction([
// Decrease on_hand + reserved at source
...receivedItems.map(item => db.inventoryLevels.update({
where: { variantId_locationId: { variantId: item.variantId, locationId: transfer.fromLocationId } },
data: { onHand: { decrement: item.quantity }, reserved: { decrement: item.quantity } },
})),
// Increase on_hand at destination
...receivedItems.map(item => db.inventoryLevels.upsert({
where: { variantId_locationId: { variantId: item.variantId, locationId: transfer.toLocationId } },
create: { variantId: item.variantId, locationId: transfer.toLocationId, onHand: item.quantity, reserved: 0 },
update: { onHand: { increment: item.quantity } },
})),
db.transferOrders.update({ where: { id: transferOrderId }, data: { status: 'received' } }),
]);
}
When an order is split across two locations, customers must be informed before confirming their order.
Shopify: Shopify's native checkout shows "Ships from multiple locations" in the shipping options when a split is needed. Customize the messaging in Settings → Checkout → Checkout language.
WooCommerce: Display a notice in the cart/checkout when ATUM detects a split is needed. ATUM Multi-Inventory includes configurable messaging for split shipments.
In the order confirmation email: Include all fulfillment groups with their expected shipping dates. "Your order will arrive in 2 shipments" with item breakdowns reduces support tickets.
Chronic stock imbalances (one warehouse overstocked, another out of stock) increase transfer costs and split fulfillments. Run a weekly analysis:
Signs of imbalance:
Action: Create a transfer order from the overstocked location to the understocked one before the next reorder cycle.
Shopify Stocky: Shows an "Inventory distribution" report that identifies imbalanced SKUs across locations.
| Problem | Solution | |---------|----------| | Split fulfillment creates two shipping charges | Consolidate shipping cost at the order level; absorb the second shipment cost or notify the customer during checkout — never charge twice silently | | Transfer received quantity differs from sent | Support partial receipt — record the actual received quantity per item and handle discrepancies (damaged in transit) separately | | Inventory double-counted across locations | Each inventory record is location-scoped; when reporting total stock, always sum across locations explicitly | | Location goes offline mid-fulfillment | Mark location as inactive; re-run allocation for unfulfilled orders assigned to that location | | Customer confused about multiple tracking numbers | Send a separate tracking email per fulfillment with a clear note: "This is shipment 1 of 2 for your order" |
tools
Let shoppers save products to a wishlist, share it with friends, and get notified when saved items come back in stock or drop in price
development
Build a themeable storefront with design tokens and CSS custom properties that supports white-labeling, multi-brand variants, and dark mode
development
Speed up product discovery with instant search suggestions, fuzzy typo matching, and category-aware results powered by Algolia or Elasticsearch
development
Build a mobile-first storefront with thumb-friendly navigation, sticky add-to-cart buttons, and touch-optimized components for high mobile conversion