Last updated: June 2026.
The moment you add a second shipping location, one question decides whether multi-warehouse helps you or hurts you: when an order comes in, which warehouse fulfils it, and does the shipping price the customer sees reflect that origin? Get that routing right and you ship faster and cheaper from the location nearest each customer. Get it wrong and you quote a Hamburg-to-Paris rate on a parcel that actually leaves Barcelona, or oversell a product that only one of your two warehouses still has. This post is specifically about how PrestaShop models warehouses, stock and carrier origin. What the core actually gives you, where it stops, and how to make order routing and origin-based shipping behave.
It is not a general "should you expand" essay. The operational case for multiple locations (geography, supplier proximity, risk) is real but well-trodden; what merchants get stuck on is the PrestaShop mechanics. So that is what we cover here.
What PrestaShop calls multi-warehouse: Advanced Stock Management

PrestaShop's native multi-location feature is Advanced Stock Management (ASM). It is not on by default. In PrestaShop 1.6 you enable it under Advanced Parameters → Configuration (the PS_ADVANCED_STOCK_MANAGEMENT config flag), then a per-product toggle on the product's Quantities tab decides whether that product uses ASM or simple stock. Once on, a Stock menu appears with Warehouses, Stock Management, Stock Movements and Stock Coverage.
The important thing to know before you build anything on it: the warehouse-based ASM was removed from the PrestaShop core back office in 1.7.0. PrestaShop 1.7.2 reintroduced a simplified, single-location stock-management page (stock movements, low-stock alerts), but not the multi-warehouse model with per-location stock and carrier sets that this post is about. Fresh 1.7 / 8.x / 9.x installs therefore do not ship the native multi-warehouse UI. Some of the legacy ASM database tables and classes may remain in the codebase, especially on shops upgraded from 1.6, but the warehouse feature is no longer exposed or maintained as a first-class part of core, and a modern warehouse module is just as likely to use its own stock and location schema rather than build on those legacy internals. If you are on a modern, clean install and you want warehouse routing, you are choosing a module or custom code from the start, not toggling a core setting. We will come back to that; first, what the native model actually does, because its data structures are still a useful reference for how modules tend to model the same problem.
How the warehouse model is wired under the hood
Knowing the moving parts saves you hours when stock numbers disagree, because PrestaShop deliberately separates "what physically sits in a warehouse" from "what a customer is allowed to buy." The pieces:
- Warehouse (the Warehouse class, ps_warehouse table), a named location with an address, a valuation method (WA / FIFO / LIFO) and an associated carrier set.
- Product-to-warehouse location (WarehouseProductLocation, ps_warehouse_product_location). Which products (and combinations) live in which warehouse, with an optional aisle/shelf location string.
- Physical stock (ps_stock), the real per-warehouse, per-product quantity, written through stock movements.
- Stock movements (ps_stock_mvt), the audit trail: every increase, decrease, transfer and the reason for it. This is your single source of truth for "where did that unit go."
- Available stock (StockAvailable, ps_stock_available), the number the storefront actually checks before letting someone add to cart. When ASM is enabled for a product and its stock dependency is active, this available quantity is derived and synchronized from the physical per-warehouse stock; otherwise ps_stock_available simply remains the storefront quantity source you set directly.
- Warehouse–carrier link (ps_warehouse_carrier). Which carriers are allowed to ship from each warehouse. This is the row that makes origin-based shipping possible at all.
So what? When your "available" quantity and your "physical" quantity disagree after a return or a transfer, you now know to look at ps_stock_mvt (the truth) versus ps_stock_available (the cached storefront figure) rather than guessing. And because the carrier set is attached to the warehouse, the origin of a parcel is a data fact in PrestaShop, not something you have to reverse-engineer at the shipping step.
The core challenge: which warehouse fulfils the order
Every multi-warehouse setup has to answer the same question, and the answer is a routing rule. PrestaShop's native ASM is closer to a stock ledger than a routing engine. It will tell you what is where, but the allocation decision is largely manual or left to a module. These are the four strategies worth knowing, and what each one costs you operationally:
| Strategy | How it decides | Wins | Watch out for |
|---|---|---|---|
| Nearest warehouse | Ship from the location closest to the delivery address | Fastest, cheapest per parcel; best customer experience | Needs the same product stocked in several places; needs a distance/zone rule |
| Stock availability | Ship from wherever the item is in stock; nearest as tie-breaker | Stops "out of stock" sales when another site has it | Can route a parcel a long way to avoid a stockout |
| Priority warehouse | Always the primary; fall back only when it is empty | Simplest to run; one team does most fulfilment | Throws away the geography benefit you built the second warehouse for |
| Split shipment | Each line ships from its own warehouse | No inter-warehouse transfers; each item leaves fastest | Two parcels, two tracking numbers, two delivery dates per order |
None is "correct" in the abstract. A store with identical stock in both sites wants nearest; a store where each warehouse holds different product lines naturally runs split shipment; a store that opened warehouse two purely as overflow wants priority. The decision that bites later is split shipment, because it changes what the customer is promised, more on that below.
Origin-based shipping: making the carrier and the rate follow the warehouse
This is the part that is genuinely PrestaShop-specific and where most "multi-warehouse" guides go vague. A carrier price in PrestaShop is driven by shipping zones, weight or price ranges, and the carrier's tax/handling settings, none of which natively know the parcel's origin. Out of the box, a carrier's price ranges are global; PrestaShop does not subtract "well, this one left Barcelona, so use the Spanish rate card." The native lever you do have is the ps_warehouse_carrier association: per warehouse, you restrict which carriers may ship from it, so a German site can offer DHL/DPD and a Spanish site SEUR/Correos, and the customer only sees carriers valid for the warehouse their order routes to.
What that does not do on its own is re-price a carrier by origin. If "DPD Standard" carries one price-range table, every order on DPD pays from that table regardless of which warehouse it left. The realistic ways to get true origin-based rates:
- Carrier-per-origin. Create distinct carriers ("DPD – DE", "DPD – ES") each with its own zones and price ranges, and associate each only with its warehouse. Crude, but it works with pure core and is auditable.
- A routing/warehouse module that resolves the fulfilling warehouse before the delivery step and presents origin-correct carriers and rates. This is the clean answer for more than two sites.
- Real-time carrier rates. If you already pull live quotes from your carriers, feed the resolved warehouse address as the origin so the quote is genuinely for that leg. For the setup mechanics of DHL/DPD/UPS specifically, see our DHL, DPD and UPS integration guide, and for multi-country GLS see GLS multi-country shipping.
The general shipping-configuration mechanics, zones, ranges, free-shipping thresholds, handling fees, are the same as for a single warehouse, just applied per carrier. We are not re-explaining them here; the full walkthrough lives in the complete PrestaShop shipping configuration guide, and if you are still installing your first carrier, start with setting up carrier modules step by step.
Keeping stock honest across locations
The operational headache of multi-warehouse is not the second building. It is keeping the numbers true. PrestaShop's separation of physical stock (ps_stock) from available stock (ps_stock_available) is helpful but also where shops drift, because returns and transfers touch one and not always the other cleanly. Discipline that holds up in PrestaShop specifically:
- Let PrestaShop be the master record. Stock changes go through Stock Management (or a module's UI), which writes a row to ps_stock_mvt. The moment a warehouse runs a parallel spreadsheet, your audit trail lies. Every adjustment should have a movement reason attached.
- Treat transfers as two movements, atomically. Moving 10 units from DE to ES is a decrement at DE and an increment at ES. If only one half writes, your available total looks fine while a physical site is wrong. Use a transfer flow that books both legs together.
- Set safety stock per product per warehouse. A global low-stock threshold is useless when site A has 40 and site B has 0 of the same SKU. Per-warehouse minimums are what trigger a transfer or reorder before B can oversell.
- Reconcile available against physical on a schedule. Because ps_stock_available is effectively a cached aggregate, a botched return or a half-finished transfer leaves it out of step with the sum of ps_stock. A periodic recount, posted as movements, is the only thing that resets reality.
If your shop is on a 1.6 install (or a 1.7+ shop upgraded from one) that still carries the legacy ASM tables, you can surface that drift with a read-only query before you go hunting by hand. This finds the mismatches, it never writes, so it's safe to run, but always take a backup before any correction it points you toward:
-- Products where the cached storefront quantity (ps_stock_available)
-- disagrees with the sum of real physical stock (ps_stock).
-- Read-only diagnostic. Replace the ps_ prefix if yours differs.
-- Caveat: this is a legacy-ASM diagnostic (ps_stock is the old Advanced
-- Stock Management table) and it does NOT handle per-warehouse / multishop
-- warehouse association – treat the drift as a flag to investigate, not a
-- definitive per-shop balance.
SELECT
sa.id_product,
sa.id_product_attribute,
sa.quantity AS available_cached,
COALESCE(SUM(s.physical_quantity), 0) AS physical_total,
sa.quantity - COALESCE(SUM(s.physical_quantity), 0) AS drift
FROM ps_stock_available sa
LEFT JOIN ps_stock s
ON s.id_product = sa.id_product
AND s.id_product_attribute = sa.id_product_attribute
WHERE sa.id_shop > 0 -- per-shop rows only; skip the global aggregate row
GROUP BY sa.id_product, sa.id_product_attribute, sa.quantity
HAVING drift <> 0
ORDER BY ABS(drift) DESC;
A non-zero drift row is a product whose storefront number no longer matches what the warehouses physically hold, exactly the line that oversells or quietly hides sellable stock. On a modern install without the legacy ASM tables, the same reconciliation is your warehouse module's job rather than a hand-written query.
What the customer sees, and where it goes wrong
From the buyer's side, multi-warehouse should be invisible. They chose a product; they should not have to know it lives in two countries. The two places the abstraction leaks, and both are avoidable:
- The delivery date. The same SKU can carry different lead times depending on which warehouse ships it, so a fixed "3–5 days" promise is wrong half the time. The date the customer is shown should follow the resolved origin. The mechanics of computing and displaying a realistic, per-origin estimate are their own topic, see setting realistic delivery-date expectations.
- Split orders. The moment one order ships from two warehouses, the customer gets two parcels, two tracking numbers and possibly two delivery days for what they paid for as one order. That is a communication problem, not a logistics one: tell them up front it is arriving in two shipments and give each its own tracking. How to wire tracking and notifications so two parcels don't read as a mistake is covered in tracking numbers and delivery notifications.
When multi-warehouse is actually worth it
A second warehouse multiplies your operational surface, more stock to reconcile, more carrier rules, more routing decisions. It earns that cost in specific situations, not as a default growth move:
- A meaningful share of your customers sit far from your current location, so they wait longer and pay more. The geography is genuinely splitting your map.
- Your single site has become a dispatch bottleneck at peak, not just on paper.
- You are entering a new market where local stock changes the delivery promise from "international" to "domestic."
- Your product range has diversified to where one facility can't store it well (climate, bulk, hazmat).
If you are dispatching a couple of dozen orders a day from one room, multi-warehouse is premature. A well-run single location beats a sloppy two-site setup every time, and the cheaper wins are usually in your shipping pricing strategy rather than your floor space. Before you sign a second lease, it is worth re-reading shipping strategy for small stores. When you are genuinely ready, open with two locations, get routing, stock sync and carrier-origin clean, and only then add a third.
Doing it on a modern PrestaShop without fighting the core
Because the native warehouse model was dropped from the 1.7+ core back office, the practical routes on a current shop are: stand up parallel origin-specific carriers and manage allocation by hand (fine for two sites, painful past that), or run a warehouse/stock module that gives you per-location stock with a real movement audit trail, picking and shipments, and per-warehouse carrier sets. That's the route our own Warehouse Revolution module takes, multi-warehouse stock positions (warehouse → rack → cell), stock movements, order fulfilment with picking, supplier and purchase-order management, and returns processing with grading, so the location an item ships from, and the carriers valid for it, are data facts rather than something you reconcile by hand. Pair it with origin-specific carriers when you need true origin-based rates, since re-pricing a single carrier by origin still isn't something core does on its own. Whichever route you pick, the PrestaShop-specific checklist is the same: warehouses defined with their carrier sets (ps_warehouse_carrier), a routing rule chosen on purpose, physical-vs-available stock reconciled on a schedule, and the delivery date plus any split-shipment messaging following the true origin.
Multi-warehouse PrestaShop FAQ
Does modern PrestaShop have built-in multi-warehouse?
No, not as a first-class core feature. The warehouse-based Advanced Stock Management was removed from the core back office in 1.7.0; 1.7.2 reintroduced only a simplified single-location stock page (movements, low-stock alerts), not the multi-warehouse model with per-location stock and carrier sets. Fresh 1.7 / 8.x / 9.x installs therefore don't ship the native warehouse UI, so on a clean modern shop you're choosing a module or custom code from the start, not toggling a setting.
How does PrestaShop decide which warehouse fulfils an order?
Out of the box, it largely doesn't. Native ASM is a stock ledger, not a routing engine. You choose a strategy and a module (or code) enforces it: nearest warehouse (fastest and cheapest, but needs the same product stocked in several places), stock availability (ship from wherever it's in stock), priority (always the primary, fall back when empty), or split shipment (each line ships from its own warehouse). None is universally correct, pick the one that matches how your sites actually hold stock.
Can PrestaShop charge shipping based on which warehouse the parcel leaves?
Not by re-pricing a single carrier. A carrier's price ranges are global, and core won't subtract "this one left Barcelona, use the Spanish rate." The native lever is the ps_warehouse_carrier link, which restricts which carriers may ship from each warehouse. For true origin-based rates you create distinct carriers per origin ("DPD, DE", "DPD, ES") each with its own zones and ranges, use a routing module that resolves the warehouse before the delivery step, or feed the resolved warehouse address as the origin to live carrier quotes.
Why do my "available" and "physical" stock numbers disagree?
Because PrestaShop separates physical stock (ps_stock, the real per-warehouse quantity) from available stock (ps_stock_available, the cached figure the storefront checks). A botched return or a half-finished transfer touches one and not the other, so they drift. ps_stock_mvt is the audit trail and your single source of truth; the read-only diagnostic above flags drifting products, and a scheduled recount posted as movements is what resets reality.
When is a second warehouse actually worth it?
When the geography genuinely splits your map (a real share of customers sit far away and wait longer), when one site is a dispatch bottleneck at peak, when local stock turns a delivery promise from "international" to "domestic," or when your range can't be stored well in one facility (climate, bulk, hazmat). If you're dispatching a couple of dozen orders a day from one room, it's premature. A well-run single location beats a sloppy two-site setup, and the cheaper wins are usually in your shipping pricing.
Multi-warehouse stops being a headache the moment those four things are deterministic instead of hopeful. The customer never sees the machinery. They just get a parcel that left the nearest building, at a price that matches the trip, on the day you promised. That is the whole point of doing it on PrestaShop properly rather than bolting a second warehouse onto a single-origin shipping setup and hoping the rates roughly even out.