logo
  • Product
  • Solutions
  • Developers
  • Resources
  • Pricing
  • Marketplace
  • Log in
  • Create a store
  • Product

  • Pricing
  • Marketplace
  • Try for free
  • Log In
  • Merchandising

  • Operations

  • Building

  • Integrations

  • Products

    Powerful modeling and versatile presentation of your entire catalog.

  • Subscriptions

    Sell recurring physical and virtual products alongside one-time offerings.

  • Discounts

    Get the sale with coupons, BXGY promotions, and automatic discounts.

  • Wholesale

    Sell B2B like it's DTC, along with volume pricing, customer groups, and invoicing.

  • Content

    Manage all your products content through the admin dashboard.

  • Users

    Multi-store admin accounts and role-based permission controls.

  • Customers

    Manage customer info, generate reports, and see buyer activity.

  • Orders

    Edit orders anytime and get the right information for smooth fulfillment.

  • Fulfillment

    Ship from multiple locations, track inventory, and split shipments.

  • Reporting

    Monitor your store's performance to ensure you have visibility across the business.

  • Storefronts

    Swell storefronts are fully customizable, allowing you to create just the right experience.

  • Checkouts

    Use our hosted checkout, integrate with a partner, or build a custom flow.

  • Payments

    Connect multiple gateways simultaneously, store cards, and split payments.

  • Internationalization

    Go global with region-specific languages, pricing, and payment methods.

No-code integrations

Connect with 40+ services for marketing, payments, fulfillment, automation, and more.

See all integrations →

Use Cases

  • Direct-to-consumer

    Tell your story and give customers a unique shopping experience

  • Subscriptions

    Sell personalized subscription bundles, memberships, and one-time items together

  • B2B/B2C

    Support retail and wholesale customers from one catalog and dashboard

  • Marketplaces

    Create a B2B or B2C marketplace with multi-vendor carts and split payouts

Customer Stories

  • Spinn Coffee

    A coffee revolution sparked by a connected machine and marketplace

  • Smashing magazine

    Global tax and shipping for complex product bundles

  • Infinitas Learning

    Delievering leading educational experiences in Europe

All customer stories →

Documentation

  • Quickstart

  • Backend API reference

  • Frontend API reference

  • Guides

  • Core concepts

  • Storefronts

Community

  • GitHub

  • Discussion forum

  • Changelog

  • API status

Resources

  • Help Center

    The latest industry news, updates and info.

  • Customer stories

    Learn how our customers are making big changes.

  • Become a partner

    For agencies creating innovative commerce experiences.

Latest blog posts

  • deep-dive-app-functions
    Nov 06, 2025

    Build smarter workflows with App Functions

  • storefronts-v2-and-the-future-of-swell-apps
    Oct 22, 2025

    Storefronts V2 and the future of Swell Apps

  • Changelog

  • API Status

  • Contact us

Blog

Migrating from PrestaShop to Swell: A Complete Guide

A mechanics-first PrestaShop to Swell runbook: multi-shop and multi-language extraction SQL, field mapping, ID-based 301 maps and the EU VAT layer.

Swell Team | October 08, 2026

One PrestaShop product in a two-shop, two-language store is not one row. It is rows in at least seven tables: `ps_product`, `ps_product_shop`, `ps_product_lang`, `ps_product_attribute`, `ps_product_attribute_combination`, `ps_stock_available` and `ps_specific_price`, keyed on composite `id_shop` and `id_lang` columns. That fan-out is what makes PrestaShop migrations go wrong, and it goes wrong quietly.

PrestaShop states that more than 230,000 stores run on PrestaShop worldwide, generating €22 billion in GMV. The current release is PrestaShop 9.1, published on 23 March 2026, which supports PHP 8.1 through 8.5 and ships Hummingbird 2.0 as the default theme. The previous line is winding down: PrestaShop 8.2.x is in its extended support phase and now receives only security and critical fixes.

This is a mechanics runbook, not a platform overview. It covers extraction, field mapping, the redirect map and the European compliance layer, the parts that genuinely differ from a WooCommerce or Magento move. Swell's pricing tiers, storefront options, import ordering and 301 hygiene are in the WooCommerce migration guide and the Shopware migration guide rather than repeated here.

Key Takeaways

  • A single product spans seven or more tables. Translation tables end in `_lang`, per-shop tables end in `_shop`, and `id_lang` and `id_shop` are the discriminating columns, as the developer documentation states. An export taken in one shop and one language returns a complete-looking file missing most of the catalogue.
  • The built-in CSV tool imports, it does not round-trip. PrestaShop's importer accepts 11 entity types including Categories, Products, Combinations and Customers, with no matching exporter. SQL Manager, restricted to SELECT queries, is the practical export route.
  • The Webservice API is XML-first and asymmetric. It can emit JSON but does not read JSON input, permissions are toggled per resource, and filters use a bracket syntax such as `filter[id_customer]=[1|5]`.
  • The 301 map is derivable from SQL, not from a crawl. Product URLs carry the record ID, in a core route of `{id}{-:id_product_attribute}-{rewrite}.html` defined in Dispatcher.php, and the rewrite is stored per language and per shop, so the map generates at one row per product per language per shop.
  • Prices are stored tax-excluded and computed at six decimals. PrestaShop offers three rounding types: on each item, on each line (the default) and on the total, per the price calculation specification. Picking the wrong one shifts every historical order total by cents.
  • Effort scales with shops × languages × combinations, not SKU count. An 18,000-SKU catalogue across 3 shops and 5 languages produces 270,000 product-language-shop rows before combinations are counted.

Why Teams Leave PrestaShop

PrestaShop earns credit where it is due. It is free to download, it has a large European agency network, its back office is translated and tax-aware out of the box in a way Anglo-centric platforms are not, and its multi-shop feature does something most competitors charge for. The reasons teams leave are specific to how it is extended and upgraded.

The override system is exclusive by design

Modules can replace core class behaviour by dropping files into an `/override` directory. The documentation is blunt: "Overrides in PrestaShop are exclusive. This means that if your module overrides one of PrestaShop's behaviors, another module will not be able to use that behavior properly", and it adds that using an override in a distributed module is not recommended, and that overrides are forbidden in partner modules.

The customisations a long-running shop depends on therefore live in local overrides written by whoever built the shop: outside the marketplace, outside module validation, and first to break on a major upgrade. It is why an upgrade quote and a replatform quote often land closer together than merchants expect.

Major versions remove features, not only APIs

PrestaShop 9.0 did more than raise the PHP floor to 8.1. It removed Advanced Stock Management entirely, including warehouses and supply orders, removed multi-address delivery, and dropped Guzzle, Swift Mailer and several Symfony components from core, so modules relying on them must now bundle them, all documented in the 9.0 core updates.

For a merchant who built warehouse logic on Advanced Stock Management, the upgrade path is a data migration into something else, which is the same work as leaving. That is the moment most PrestaShop replatform projects actually start.

How PrestaShop Stores One Product

The tables behind a single SKU

  • `ps_product` holds the shop-agnostic core: `reference` (the SKU), `price` tax-excluded, `id_tax_rules_group`, `ean13`, `weight`, `id_category_default`.
  • `ps_product_lang` holds one row per product per shop per language: `name`, `description`, `description_short`, `link_rewrite`, `meta_title`, `meta_description`.
  • `ps_product_shop` holds per-shop overrides: `active`, `price`, `id_category_default`, `visibility`. A product can be active in the French shop and disabled in the German one.
  • `ps_product_attribute` holds combinations with their own `reference`, `price` impact and `ean13`.
  • `ps_product_attribute_combination` is the junction between combinations and attribute values, holding one row per combination per attribute. A product built from four attribute groups produces four rows per combination: the count is combinations times attribute groups, not combinations alone.
  • `ps_stock_available` holds quantity per product, per combination and per shop, and is the authoritative source for stock; duplicated values on the product object exist only for backward compatibility.
  • `ps_specific_price` holds discounts and contextual pricing, scoped by `id_shop`, `id_currency`, `id_country`, `id_group`, `id_customer` and `from_quantity`.

One trap for anyone writing raw SQL: the `ps_` prefix is a convention, not a guarantee, because PrestaShop 9 generates a randomised table prefix at install time by default. Read the prefix from the installation's configuration and parameterise it.

An extraction query that does not lie

The honest extraction is a join producing one row per product, per shop, per language, per combination. The verbosity is the point.

```sql
SELECT
p.id_product,
p.reference AS sku,
p.price AS price_tax_excl,
p.id_tax_rules_group,
psh.id_shop,
pl.id_lang,
pl.name,
pl.link_rewrite,
psh.active AS active_in_shop,
pa.id_product_attribute,
pa.reference AS combination_sku,
pa.price AS combination_price_impact,
sa.quantity AS stock_for_shop,
GROUP_CONCAT(pac.id_attribute ORDER BY pac.id_attribute) AS attribute_ids
FROM ps_product p
JOIN ps_product_shop psh
ON psh.id_product = p.id_product
JOIN ps_product_lang pl
ON pl.id_product = p.id_product
AND pl.id_shop = psh.id_shop
LEFT JOIN ps_product_attribute pa
ON pa.id_product = p.id_product
LEFT JOIN ps_product_attribute_combination pac
ON pac.id_product_attribute = pa.id_product_attribute
LEFT JOIN ps_stock_available sa
ON sa.id_product = p.id_product
AND sa.id_product_attribute = IFNULL(pa.id_product_attribute, 0)
AND sa.id_shop = psh.id_shop
GROUP BY p.id_product, psh.id_shop, pl.id_lang, pa.id_product_attribute
ORDER BY p.id_product, psh.id_shop, pl.id_lang;
```

Compare the row count against the product count shown in the back office. A 6,000-SKU catalogue in one shop with four languages returns roughly 24,000 rows before combinations multiply it. If the two numbers are close rather than a clean multiple, the join is wrong or the catalogue is not associated to every shop. Every extraction should carry `id_shop` and `id_lang` as explicit columns, and the import script should refuse any file that lacks them.

The Three Export Paths and Where Each Breaks

Advanced Parameters, Import: an importer, not a round trip

PrestaShop's CSV tooling is documented as an import feature covering "Categories, Products, Combinations, Customers, Addresses, Brands, Suppliers, Aliases, Supply orders, Supply order details, Store contacts", and it handles a single language per file, so multilingual data must be split across files. There is no symmetric exporter, and orders, order details and invoices are not importable at all.

Where it breaks: no order history, no per-shop associations, no second language. It is a way to reason about PrestaShop's field names, not an export path.

SQL Manager: the practical export route

SQL Manager, under Advanced Parameters, Database, saves a query and downloads its result as CSV. The constraint is firm and, for a migration, harmless: "For security reasons, some types of queries are not allowed: UPDATE, DELETE, CREATE TABLE, DROP, etc. You can only read data", and the tool exposes a default file encoding setting with UTF-8 recommended.

Where it breaks: it runs inside a PHP request, so large joins on shared hosting time out or exhaust memory. Chunk by `id_product` ranges, or take a `mysqldump` and run the same queries locally.

The Webservice API: XML-first, permission-gated, awkward under multi-shop

The Webservice exposes a resource list at `/api/` including `products`, `combinations`, `specific_prices`, `stock_availables`, `orders`, `tax_rules`, `shops` and `shop_urls`, with per-resource permission toggles on each access key. XML is the default, and the asymmetry matters for tooling: JSON can be requested for output, but the Webservice cannot read JSON inputs.

Filtering uses `filter[fieldname]=[value]` with bracket operators: `[1|5]` for OR, `[1,10]` for an interval and `%[oh]%` for contains. Sorting uses `sort=[lastname_ASC]`, paging uses `limit=[offset,limit]`, and date fields require `&date=1` appended before they can be sorted or filtered.

Multi-shop is where it becomes painful. Requests take an `id_shop` or `id_group_shop` parameter, but a new shop has no content associated at first, and complex resources such as categories and products require cleaning extra fields or copying them into a blank schema. Reading a catalogue out of a three-shop store means three passes and a reconciliation step, slower and less reliable than one SQL join. PrestaShop 9's separate Admin API, built on API Platform with OAuth2, is still expanding its coverage, so check it against the installed version first.

Mapping PrestaShop Entities to Swell Models

For this specific migration shape, Swell maps unusually cleanly, and the reason is structural. PrestaShop's hard problem is that translation, per-shop variation and contextual pricing each live in a separate parallel table. Swell collapses two of those into field-level modifiers on the same record: any string field accepts alternative values through the `$locale` parameter, and per-currency prices are set and read through `$currency`. Subscriptions and recurring billing sit in the core API rather than a purchased module. The counterweight is that Swell has no equivalent for PrestaShop's shop-group sharing model, covered below.

Products and translations

  • `ps_product.reference` maps to `sku` on the Swell product model, which is optional. PrestaShop rows with an empty `reference` therefore import without error, so add the emptiness check to the transform rather than relying on the API to reject them.
  • `ps_product_lang.name` for the default language maps to `name`; every other `id_lang` row goes into the same record via `$locale`, not into a second product.
  • `link_rewrite` maps to `slug`, which is required and unique. PrestaShop stores one rewrite per language, so choose: one canonical slug, or locale-specific slugs handled at the storefront layer.
  • `description` and `description_short` map to the purchase option `description` and to `summary`, `meta_title` and `meta_description` map across unchanged, and `price`, which is tax-excluded, maps to `purchase_options.standard.price` with `id_tax_rules_group` becoming `tax_class`.
  • `ean13`, `upc`, `isbn` and feature values from `ps_feature_value_lang` map to `attributes`, an open key/value object.
  • `ps_product_shop.active` per shop has no single-field equivalent. Either the shop becomes a separate Swell store, or the flag becomes an attribute driving storefront visibility.

Combinations to variants

A combination is defined by its rows in `ps_product_attribute_combination`, each pointing at an `id_attribute` in an `id_attribute_group`. Swell defines `options` on the parent product and identifies each variant by `option_value_ids`. Two details matter. `ps_product_attribute.price` is a price *impact* added to the parent price, whereas a Swell variant `price` overrides the parent outright, so it has to be resolved during transform. And Swell archives a sold variant automatically when options later change in a way that would remove it, so a re-run produces archived records rather than errors.

Stock, per shop

`ps_stock_available` carries `quantity`, `physical_quantity`, `reserved_quantity` and `out_of_stock` alongside `id_shop` and `id_shop_group`. Swell exposes `stock_level` as the sum of stock entries, with `stock_tracking`, `stock_backorder`, `stock_preorder` and `stock_purchasable` as separate booleans. The `out_of_stock` integer encodes deny, allow or use-default, so it maps to `stock_backorder` plus store defaults rather than to one field.

Specific prices to price rules

This is the mapping most likely to lose data quietly. The `specific_prices` resource carries `id_shop`, `id_product`, `id_product_attribute`, `id_currency`, `id_country`, `id_group`, `id_customer`, `price`, `from_quantity`, `reduction`, `reduction_type`, `from` and `to`, all documented in the resource reference. European stores lean on it heavily, for net pricing to VAT-registered business customers by `id_group`, country-specific prices and currency-specific rounding.

Swell's `prices` array supports `price`, `account_group`, `quantity_min` and `quantity_max`, so `id_group` becomes `account_group` and `from_quantity` becomes `quantity_min`, while currency maps through `$currency`. The conditions that do not map are `id_country` and `id_customer`: country-scoped prices become a tax and currency decision or a storefront rule, and single-customer prices are better modelled as a dedicated account group. Run a distinct count of `id_group`, `id_country` and `id_currency` first, because that count is the real complexity number.

What does not survive

  • Module data in proprietary tables. Modules create their own tables with no schema contract. Loyalty points, custom fields, review bodies and configurator state all need bespoke extraction, and the module author's documentation is usually the only reference.
  • Overrides and Smarty theme templates. Files in `/override` and the theme's `.tpl` files have no target. The behaviours they implement have to be re-specified, the largest hidden line item in most quotes.
  • Advanced Stock Management data. Removed from core in 9.0, so on a 9.x source it may already be gone. On an 8.x source it needs a destination decision.
  • Generated invoice PDFs. Covered below. They are documents, not records.
  • Configuration rather than data. Carrier weight and price ranges in `ps_carrier`, cart rules restricted by carrier, country, group and date, `ps_customer_thread` and `ps_customer_message`, and employee permission profiles are rebuilt in the new backend rather than transferred. Password hashes do not transfer either, so a reset flow at launch is assumed throughout.

For import sequencing, the batching endpoint and the caveat about the `$migrate` flag and subscription billing automation, follow the procedure in the Shopware migration guide.

Rebuilding the Redirect Map from SQL

PrestaShop's friendly URLs carry the record ID, which merchants have complained about for a decade. For a migration it is a gift: the old URL is a pure function of database columns, so the map is generated rather than crawled, and complete by construction rather than complete to the crawler's depth.

The routes actually being redirected

  • Products: the core route is `{id}{-:id_product_attribute}-{rewrite}.html`, while the back office default schema is `{category:/}{id}-{rewrite}{-:ean13}.html`, producing paths such as `/summer-dresses/7-printed-chiffon-dress.html`, documented in the SEO and URLs settings.
  • Categories: `{id}-{rewrite}`, with parent paths prepended when nested. CMS pages: `content/{id}-{rewrite}`. Brands: `brand/{id}-{rewrite}`. Suppliers: `supplier/{id}-{rewrite}`.
  • Module front controllers: `module/{module}{/:controller}`. These are the defaults defined in Dispatcher.php, and any can be customised per shop, so read the live route configuration rather than assuming.

Generating the map

The query below returns the components of every product URL, one row per product per shop per language. Assemble the final string in the transform script so the language prefix and any custom route schema are applied in one place.

```sql
SELECT
su.id_shop,
su.domain,
CONCAT(su.physical_uri, su.virtual_uri) AS base_uri,
l.iso_code AS lang,
cl.link_rewrite AS category_rewrite,
p.id_product,
pl.link_rewrite AS product_rewrite,
CONCAT(p.id_product, '-', pl.link_rewrite, '.html') AS old_leaf
FROM ps_product p
JOIN ps_product_shop psh ON psh.id_product = p.id_product
JOIN ps_shop_url su ON su.id_shop = psh.id_shop AND su.main = 1
JOIN ps_lang l ON l.active = 1
JOIN ps_product_lang pl ON pl.id_product = p.id_product
AND pl.id_shop = psh.id_shop
AND pl.id_lang = l.id_lang
JOIN ps_category_lang cl ON cl.id_category = psh.id_category_default
AND cl.id_shop = psh.id_shop
AND cl.id_lang = l.id_lang
WHERE psh.active = 1
ORDER BY psh.id_shop, l.id_lang, p.id_product;
```

The join to `ps_shop_url` is what makes this work across domains: it holds `domain`, `physical_uri`, `virtual_uri`, `main` and `active` per shop, so a three-country store on three domains produces correctly hosted URLs in one pass. Repeat the pattern against `ps_category_lang` and `ps_cms_lang`.

Meta pages and the tail nobody remembers

`ps_meta` and `ps_meta_lang` hold the rewrite for controller-backed pages such as contact, sitemap, stores and new products, one row per language. These generate real indexed URLs and are missed by product-and-category-only redirect maps. Check one more behaviour before cutover: PrestaShop may already be issuing internal canonical redirects as 301 or 302 when the rewrite in a requested URL does not match the stored value, and a live setting of 302 means search engines may be holding the non-canonical variant. For the rest of the redirect discipline, follow the WooCommerce migration guide.

The European Layer

This is where a PrestaShop migration diverges most from an American replatform, and where most post-launch incidents originate.

Tax-excluded prices and tax rules groups

PrestaShop stores the catalogue price tax-excluded and attaches an `id_tax_rules_group` to each product. That group is a set of `ps_tax_rule` rows, each carrying `id_country`, an optional `id_state`, an optional `zipcode_from` and `zipcode_to` range, an `id_tax` and a `behavior` flag governing how overlapping rules combine.

Swell orders carry an explicit `item_tax_included` boolean and per-item `taxes` arrays, documented on the order model. The mapping is simple. The risk is that a team assumes the numbers are gross because that is what the storefront showed, and inflates the catalogue by the VAT rate. Validate ten products by hand against the back office price field before running at volume.

Rounding is a data decision, not a display setting

PrestaShop computes with six decimal digits and exposes two independent settings: a round mode, and a round type of round on each item, round on each line (the default) or round on the total. With round on each item, the unit price tax included of each product is rounded before making calculations when taxes are used, and the unit price tax excluded when they are not.

Historical totals appear on issued invoices a tax authority may inspect, and the round type may have changed during the store's life. Import totals as literal values rather than recomputing them, and reconcile the sum against the PrestaShop figure for the same period before decommissioning anything.

OSS and destination-country VAT after cutover

Cross-border B2C selling inside the EU is governed by a single threshold. The European Commission states that "The previous thresholds for distance sales of goods within the EU have been abolished and replaced by a new EU-wide threshold of EUR 10 000", above which VAT is due in the customer's member state and can be declared through the One Stop Shop with a single registration.

If the new store calculates destination VAT through a tax service, the `ps_tax_rule` rows become reference data for reconciliation rather than something to import. Either way, run a parallel calculation for a representative basket per destination country and compare to the cent. Swell customer accounts carry `vat_number` and a `type` of individual or business, the hook for reverse-charge handling.

Invoices, and what happens when the back office is switched off

Article 226 of the VAT Directive sets the mandatory particulars of an invoice: date of issue, a sequential number that uniquely identifies it, the supplier's VAT identification number, the customer's details, and the quantity and nature of what was supplied. Article 247 leaves the storage period to each member state, and those periods vary by years.

PrestaShop generates invoice PDFs from order data rather than storing a signed document per order. The batch tools produce combined output, and the documentation is explicit: "the invoices are generated into a single PDF file, each with their own pages. You cannot get a single PDF file for each invoice", with an optional setting to use the disk as the cache for PDF invoices, which saves memory but slows generation and still stores nothing permanently.

That combination is the trap. Switching off the instance removes the only thing that can regenerate a compliant invoice, including the sequential number and the legal footer. A serious migration does two things. Export every historical invoice to individual PDFs before decommissioning and archive them for the national retention period. Then carry the invoice data into the new backend so support staff can answer queries without restoring a database. Note that Swell's invoice model is a subscription billing artefact, created when a subscription is charged, so it is not the destination for historical one-off order invoices. Land `ps_order_invoice`'s number, tax breakdown and totals on the imported order record instead; the archived PDF remains the legal artefact.

The withdrawal right and how order states encode it

EU consumers have a 14-day right of withdrawal on distance contracts, with the clock running from delivery. PrestaShop encodes the workflow in `ps_order_state` rows carrying boolean flags including `logable`, `paid`, `shipped`, `invoice`, `pdf_invoice` and `send_email`.

Two of those are destructive if mapped carelessly. Once an order is flagged paid or shipped, PrestaShop does not allow a return to the previous status, so `ps_order_history` is the only record of how an order actually progressed. Import the history, not only the final state, or the evidence that a refund was issued within the statutory window disappears. Directive (EU) 2023/2673 also introduces a prominently displayed withdrawal function on online interfaces from 19 June 2026, which a storefront rebuild is the cheapest moment to implement.

GDPR for customer data in transit

A migration moves the entire customer table across at least one network boundary, often onto a developer laptop. Extract over an encrypted channel, keep the dump encrypted at rest with a documented deletion date, and drop fields with no destination: `ps_customer.passwd` hashes, IP addresses in `ps_guest` and `ps_connections`, and abandoned-cart rows for accounts that never converted. PrestaShop's official GDPR module, psgdpr, defines the hooks through which modules expose personal data for a subject access request, so its configuration doubles as an inventory of which modules hold customer data. Its README states it is only partially compatible with multi-shop, so verify that inventory manually.

Reality Check

Payments do not cut over cleanly in Europe

European PrestaShop stores rarely run a single gateway. They run a card gateway plus SEPA direct debit plus local methods such as iDEAL, Bancontact, Cartes Bancaires or BLIK, each through a separate module storing tokens and mandate references in its own tables. Card token portability is a solved problem with a defined process, documented in the WooCommerce migration guide. SEPA mandates are not: a mandate is a legal authorisation tied to a creditor identifier, and changing the creditor generally requires re-authorisation. Confirm this with the provider before promising uninterrupted recurring collection.

Costs that do not appear in the project plan

Three line items are consistently underestimated. Re-specifying override behaviour, because the requirement usually exists only in the code. Extracting module-owned tables, because each module is a bespoke reverse-engineering job. And invoice archival, because nobody owns it until someone asks for a two-year-old invoice. Swell's own cost is revenue-tier based with a per-order overage above the included volume; the tier table is explained in the WooCommerce guide.

A testing checklist that catches PrestaShop-specific failures

  • Row-count reconciliation per shop and per language, not per product: compare `COUNT(*)` from the extraction query against imported records grouped the same way.
  • Order total reconciliation for one full financial period, against the PrestaShop figure to the cent.
  • Redirect map coverage: every row from the URL query resolves with a single 301 and no chain, tested per domain and per language prefix.
  • Destination-country VAT on a representative basket for every country the store ships to.
  • Invoice retrieval for a sample of historical orders from the archive, not from the new backend.

Effort Estimates by Shops, Languages and Combinations

Catalogue size alone is a poor predictor. The multiplier that matters is shops × languages × combinations, because it drives extraction row counts, mapping decisions and testing surface at once. Three worked profiles, with the Adobe Commerce migration guide covering the general shape of legacy-PHP project effort.

  • One shop, one language, 800 SKUs. Roughly 800 product-language-shop rows and a few thousand combination rows. Extraction is a single query and the redirect map is a few thousand lines. Two to three weeks with one developer, most of it on the storefront.
  • One shop, four languages, 6,000 SKUs, average six combinations. Around 24,000 product-language rows and roughly 36,000 variants. Translation coverage is the main risk: incomplete `ps_product_lang` rows are common on stores that added a language later and never finished, and the gaps are invisible in the back office default language. Count rows per `id_lang` and flag every product below the language count before transform. Four to seven weeks with one to two developers.
  • Three shops, five languages, 18,000 SKUs, average twelve combinations. Around 270,000 product-language-shop rows and over 200,000 combinations, across three domains and three tax configurations. Extraction moves onto a database dump, and the redirect map alone runs to several hundred thousand lines needing per-domain deployment. Eight to fourteen weeks with two to three developers.

The multi-shop decision with no direct equivalent

PrestaShop shop groups can share customers, available quantities and orders across shops in the group, and Swell has no equivalent construct. One Swell store with locale and currency switching handles the common case: shops that existed to serve different languages, currencies or domains with substantially the same catalogue. Customers, orders and stock stay unified, which is what a sharing-enabled shop group already implied, and per-shop `active` flags become storefront logic. Separate Swell stores suit genuinely separate businesses with different catalogues, legal entities or VAT registrations, at the cost of shared accounts and stock. A group that shares customers but not stock usually resolves to one store plus location-aware inventory. The decision changes the extraction query, so make it first.

Frequently Asked Questions

Can PrestaShop's built-in CSV export migrate a multi-language catalogue?

No. The CSV tooling is built for import and handles a single language per file. The back office list Export button returns the columns currently displayed, filtered by the active shop context and rendered in the employee's language, with nothing in the file recording which shop or language it came from. Use SQL Manager or a database dump instead.

Will existing PrestaShop product URLs keep working?

Only if they are redirected, and PrestaShop makes that unusually tractable. Because the product ID is embedded in the URL and the rewrite is stored per language and per shop, the full set of old URLs can be generated from the database rather than crawled. Join `ps_product`, `ps_product_lang`, `ps_product_shop` and `ps_shop_url`, emit one row per product per language per shop, and map each to its new path.

What happens to historical invoices when the PrestaShop instance is switched off?

They stop being reproducible. PrestaShop generates invoice PDFs from order data at request time rather than storing one document per order, and its batch export produces a combined file. Export every historical invoice as an individual file before decommissioning and archive it for the retention period that applies in the relevant member state, since the VAT Directive leaves that period to national law. Landing the invoice number, tax breakdown and totals on the imported order record gives support staff searchable data, but the archived PDF is the legal artefact.

Does a PrestaShop multi-shop install need one Swell store per shop?

Usually not. Shops serving different languages, currencies or domains from a broadly shared catalogue map to one Swell store using `$locale` and `$currency`, which preserves the unified customers and orders a sharing-enabled shop group already implied. Separate stores make sense for genuinely separate businesses with different catalogues, legal entities or VAT registrations.

Is it worth upgrading to PrestaShop 9 before migrating away?

Generally no, and sometimes it is actively harmful. PrestaShop 9.0 removed Advanced Stock Management including warehouses and supply orders, along with multi-address delivery, so upgrading first can destroy data the migration needs. Extract from the version currently running. The exception is a store already mid-upgrade with a working 9.x staging environment, where the newer schema avoids maintaining two sets of queries.

Next-level commerce for everyone.

X.comGitHubLinkedIn

Subscribe to our newsletter for product updates and stories

Subscribe

Resources

Help CenterDeveloper CenterCommunityAgenciesChangelogLearn

Use cases

SubscriptionsB2B WholesaleMarketplaceOmnichannelDirect-to-consumerEnterprise

Explore

FeaturesPricingIntegrationsCustomer stories

Developers

OverviewDocumentationGuidesStorefrontsHeadlessSwell Apps

Company

BlogAbout usPartnersContact us

© 2026 Swell. All rights reserved.

Privacy PolicyTerms of Service