OpenCart Multi-Warehouse Setup
Multi-warehouse in OpenCart allows dividing product inventory across multiple physical or logical storage points — office, supplier warehouse, dropshipping partner, regional hub. Without this setup, a store with multiple supply sources loses inventory accuracy and complicates logistics.
What is Multi-Warehouse in OpenCart
Standard OpenCart installation stores inventory in single oc_product table (quantity + subtract). To work with multiple warehouses need module adding oc_location, oc_product_location tables and logic for deduction on order.
Popular solutions:
- Journal3 Warehouse — built into Journal3 theme
- Multi Warehouse Pro from Opencart.com — official extension
- XLDevelopment module — flexible warehouse priority setup
- Custom development via Events + Model override
Solution Architecture
Basic schema for custom implementation:
CREATE TABLE oc_location (
location_id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(128),
address TEXT,
status TINYINT(1)
);
CREATE TABLE oc_product_to_location (
product_id INT,
location_id INT,
quantity INT DEFAULT 0,
PRIMARY KEY (product_id, location_id)
);
On order placement catalog/model/checkout/order/addOrder/after event triggers custom observer deducting inventory from needed warehouse by priority rule (closest to buyer, first non-empty, product-specific warehouse).
Setup via Extension
Step 1 — Install module
Upload via admin > Extensions > Installer. Module registers events via oc_event:
// install.php
$this->model_setting_event->addEvent([
'code' => 'multi_warehouse',
'trigger' => 'catalog/model/checkout/order/addOrder/after',
'action' => 'extension/multi_warehouse/model/warehouse/subtractStock',
'status' => 1,
'sort_order' => 1
]);
Step 2 — Create warehouses
In Extensions > Multi Warehouse > Locations create warehouses: name, address, priority, shipping zone mapping.
Step 3 — Distribute products
Product card gets "Warehouses" tab. For each warehouse specify available quantity. With subtract enabled system works with aggregated inventory.
Step 4 — Warehouse selection rules
Deduction strategies:
-
FIFO by priority — takes from warehouse with lowest
priority - Closest to buyer — by shipping zone mapping
- Product-specific — hard tie to warehouse
- Mixed — deducts from multiple warehouses on deficiency (split fulfillment)
Supplier Integration
For dropshipping or VMI inventory updates via API:
// Update warehouse stock via custom API endpoint
// admin/controller/api/warehouse.php
public function updateStock(): void {
$json = [];
$location_id = (int)$this->request->post['location_id'];
$updates = $this->request->post['products']; // [{product_id, quantity}]
foreach ($updates as $item) {
$this->model_extension_warehouse->setLocationStock(
(int)$item['product_id'],
$location_id,
(int)$item['quantity']
);
}
$json['success'] = count($updates) . ' products updated';
$this->response->addHeader('Content-Type: application/json');
$this->response->setOutput(json_encode($json));
}
Supplier makes POST /index.php?route=api/warehouse/updateStock with token in header — inventory updates without admin access.
Display on Frontend
By default customer sees total inventory. Can show availability by warehouse:
{% if product.locations %}
<ul class="warehouse-availability">
{% for loc in product.locations %}
<li>{{ loc.name }}:
{% if loc.quantity > 0 %}in stock ({{ loc.quantity }} units)
{% else %}out of stock{% endif %}
</li>
{% endfor %}
</ul>
{% endif %}
Relevant for B2B stores where buyer selects pickup warehouse.
Reservation and Returns
Important — reserve inventory on payment, not on order creation. Module must handle statuses:
| Order Status | Inventory Action |
|---|---|
| Pending | No change or soft reservation |
| Processing | Hard deduction |
| Shipped | Shipment confirmation |
| Cancelled | Return to warehouse |
| Refunded | Return by return warehouse rule |
Configure events on catalog/model/checkout/order/addOrderHistory/after.
Reporting
Useful SQL queries for inventory control:
-- Inventory by warehouses
SELECT l.name AS location, p.model, ptl.quantity
FROM oc_product_to_location ptl
JOIN oc_location l ON l.location_id = ptl.location_id
JOIN oc_product p ON p.product_id = ptl.product_id
WHERE ptl.quantity < 5
ORDER BY l.name, p.model;
-- Movement by warehouse for period
SELECT DATE(oh.date_added), SUM(op.quantity)
FROM oc_order_history oh
JOIN oc_order_product op ON op.order_id = oh.order_id
WHERE oh.order_status_id = 2 -- Processing
AND oh.date_added BETWEEN '2025-01-01' AND '2025-01-31'
GROUP BY DATE(oh.date_added);
Timeline and Effort
Setting up ready module with 2–3 warehouses: 2–3 days. Custom development from scratch (priorities, supplier API, split fulfillment): 1–2 weeks. Integration with external WMS (1C, MoyaSklad) adds 3–5 days for data mapping and testing.







