Skip to main content
Migration guide

Shopware to Vendure

What moves from Shopware 6 to Vendure as data, what gets rebuilt as code, and the traps that never raise an error.

Catalogue

1,514 variants

About a thousand demo products through the Admin API, checked value by value against Shopware's own APIs.

Order history

306 orders

Moved as finished records, not replayed. Every stored total equals its invoice, within a cent per discount share.

Passwords kept

151 of 151

Every registered customer logged into the Vendure Shop API with their Shopware password.

Rule prices

4 in 5

Variants whose Shopware price differs from the base price that moved. The share is demo data; the mechanism is not.

In short

Three parts, and they cost very different amounts

The catalogue moves as data through Vendure's Admin API. Customers and order history move as data too, but no API takes them, so a script running inside Vendure saves them. Everything Shopware calculates at runtime is rebuilt as code: rule prices, Rule Builder conditions, Flow Builder automations, Shopping Experiences layouts, and payment and shipping handlers. That third part is where the project cost sits.

Kevin, a Shopware extension developer, built an open-source migrator for this guide and ran it on Shopware 6.7's demo shop, so the figures and effort ranges come from that run: SpaceParrots/shopware-vendure-migrator.

Moving the data is the easy part. The challenge is thinking through the customisations and edge cases.

Kevin MattutatKevin Mattutat · Founder and developer, SpaceParrots

Is this guide for your shop?

A good fit

  • Self-hosted or PaaS Shopware 6.7
  • Your own developers, or an agency, writing the commerce logic
  • Variants, several languages and tax rates
  • Some price rules

Not a good fit

  • Prices and merchandising run in the Rule Builder, with no developers
  • Automation lives in the Flow Builder, pages in Shopping Experiences
  • A Shopware major upgrade still to finish: two platform projects in one year
  • B2B Components (Evolve and above) in use: employee accounts, roles, quotes and approvals are a project of their own, not a data migration. Vendure Core has none of them; the paid plans add them

Written for developers and tech leads judging whether a Shopware 6.7 shop can make the move, and what it takes. In Vendure each new rule or flow is code; a team without developers would trade tools it uses every day for work nobody on it can do.

Why developers move

Configuration in Shopware, code in Vendure

Kevin has built Shopware extensions since early version 6, but never run a shop. Shopware lets a merchant change prices, shipping, payment and automations without a developer, in the Rule Builder and Flow Builder. Custom work is harder: it runs through Shopware's own data abstraction layer, with its limits, and every storefront component also has to work as a Shopping Experiences block.

Vendure makes the opposite trade: the team builds the shop around its own business model, in code. That suits a model no standard shop fits, such as a marketplace.

  • The business logic is yours. Every rule Shopware offers as a no-code tool is a named TypeScript extension point in Vendure: price calculation, tax, shipping eligibility, promotion conditions, the order process (strategies).
  • Headless, so the parts scale on their own. The same API serves a web shop, an app or a point of sale, and the backend scales independently of the storefront.

Whether that trade is worth a migration is the merchant's decision. This is the code side of it.

Count before you map

Each of these counts changes the plan. Appendix A has the queries.

CountWhy it matters
Products with and without variantsEither way, each becomes one Vendure product with at least one variant
Variants that inherit from their parentEvery inherited field and association has to be resolved before loading
Rule prices, per rule and condition typeOne piece of code per condition type
Customer rows per email, and guest rowsShopware creates a row per guest checkout; Vendure keeps one customer per email
Order, delivery and payment state combinationsEach one that occurs needs a Vendure state
Order line items per typeOnly product lines become Vendure order lines
Canonical SEO URLsThe redirect map is only as complete as this table
Shopping Experiences layoutsEach one is rebuilt in the storefront or a CMS
Settings overridden per sales channelThe shop runs on the overrides in system_config, not on the defaults

Then give every plugin and integration a Vendure answer and an owner before the project starts, sorted by what it adds: data, behaviour, or a connection to another system.

The migration

The migrator in five stages

  1. 1

    Extract

    Every table in one read-only transaction, draft versions skipped.

  2. 2

    Transform

    Resolve inheritance, merge customers by email, map each order's states.

  3. 3

    Load

    The catalogue through the Admin API; customers and orders through a script inside Vendure.

  4. 4

    Verify

    Vendure against the target model, and customers and orders against Shopware's database.

  5. 5

    Compare

    The catalogue against Shopware's own Admin and Store APIs.

Every stage can run again on its own, so the cutover can be rehearsed on a copy of production as often as needed. The loader creates and skips but does not yet update changed objects: a real cutover also needs updates and a delta run for the freeze window. After the last load, clean up the job queue and rebuild the search index. Appendix B has the code.

Load

What the API cannot take needs no SQL

The catalogue goes through Vendure's Admin API. Customers and orders cannot: the API takes no password hash, creates no customer without a login, and has no order import, since a draft order reprices every line and takes a new number and today's date.

So a standalone script boots Vendure with bootstrapWorker() and the server's own config (stand-alone scripts), and saves customers and orders as Vendure entities. Custom fields, plugins and price and tax strategies still apply, and nobody writes SQL against Vendure's tables.

Pictured: a migrated product with its "Colour" option group, three variants, and facet values that came from Shopware properties.

Storefront

Rebuild the storefront, ideally from a starter

Vendure Core has no built-in storefront, only an API the shop calls, so the Shopware storefront has to be rebuilt. The reference migration did not build one. From the Scale plan, the B2B storefront is included.

You can technically reuse the Twig templates from a PHP app, or move the Vue components into a Nuxt app, and point either at Vendure's API, but every data call still has to be rewritten. The faster way is one of Vendure's storefront starters in Next.js or TanStack Start, where the basic shop already works and you adapt the design.

Plan it as its own workstream: listings, search, cart and checkout, accounts, CMS pages, SEO and transactional mail.

What ported as data, and what got rebuilt

Ported or transformed as data

  • Products, variants and option groups
  • Properties and manufacturers, as facets
  • Categories, as collections with the same tree and order
  • Media, translations, base prices and stock
  • Tax rates, regrouped into zones
  • Customers with passwords, groups and addresses
  • Orders as records: amounts, dates, states, payments, fulfilments
  • Canonical SEO URLs, as a redirect map

Rebuilt, replaced or retired

  • Rule prices: a price calculation strategy
  • Rule Builder on shipping, payment and promotions: checkers and conditions
  • Flow Builder automations: event subscribers
  • Shopping Experiences layouts: storefront or CMS
  • Product streams, and the "link only" and "search only" visibility levels
  • Payment and shipping handlers, mail templates, number ranges
  • Partial shipments and refunds, returns, invoices: kept as an archive

The right column is also what merchandisers lose as self-service. In Vendure a developer writes the condition once; merchandisers then configure promotions, shipping and payment from what exists.

Trap 1 of 4 · none raised an error

Variants inherit whole associations, not just fields

A variant inherits its tax rate from its parent, and its categories and properties as a whole set. In the reference shop, not one variant had its own.

Read the mapping tables as they are, and every variant lands in zero collections while the load reports success.

Pictured, Shopware: a variant whose name, manufacturer and description come from the parent (link icons).

Trap 2 of 4

Rule prices change what most customers pay

For a guest buying one item, more than four in five demo variants cost something other than the base price that moved to Vendure.

Shopware picks the first matching rule that has prices, by priority and then by rule ID. Not the cheapest. A rebuild needs that exact order (Appendix C), or a deliberate, announced switch to simpler prices.

Traps 3 and 4, and the quick ones

The cutover window, and the last cent

Background jobs set the cutover window, not the API

The catalogue writes took about a minute and queued thousands of search and collection jobs, which took about twenty times as long to drain.

Measure it on your own catalogue, or switch the queue off for the bulk import as the import guide describes.

Order history is records, down to the cent

Shopware keeps separate order, delivery and payment states: 288 combinations, 14 in the demo data, each needing a decision. Partial shipments and refunds cannot be rebuilt.

Shopware also rounds each tax rate's share of a discount separately, so 39 of 306 orders missed their invoice by up to a cent per share. The migrator stores the invoice total, capped at a cent per share.

Quick gotchas: export and import

  • Filter every versioned table on the live version, or draft rows end up in the export.
  • Read category members from product_category_tree, not product_category: a category page also lists its subcategories' products.
  • Rewrite Shopware's $2y$ bcrypt prefix to $2b$. Vendure's bcrypt library answers "wrong password" to it without an error.
  • JavaScript rounds -16.745 to -16.74, PHP to -16.75. Give Vendure a money strategy that rounds halves away from zero.
  • Migrate newsletter recipients as consent records, with opt-in status and date, including recipients who never had an account.

Quick gotchas: channels, prices and promotions

  • A sales channel is not a Vendure channel. Create channels where prices, assortment or tax differ, not one per domain; the storefront picks language and currency per request.
  • Shopware calculates a missing currency price from the EUR price and a factor. Vendure has no such fallback, so write the converted prices during the migration.
  • Shopware can store a net price that is not gross divided by one plus tax. Vendure stores one price per currency, so count those prices first.
  • Unit prices, which German law requires for goods sold by weight, volume, length or area (§ 4 PAngV), need custom fields and storefront support in Vendure.
  • Imported orders do not use up promotions, so a one-per-customer code works again. Link the orders to the rebuilt promotion or retire the code.

Effort: days to months, driven by the storefront

Kevin's estimates from the reference migration and his Vendure projects. AI coding agents shorten the writing, not the testing: in both runs, reviews against Shopware's source and data found errors no load error showed.

PartEffortWhat moves it
CatalogueHours to daysCatalogue size, languages, custom fields
Customers and order historyDays, longer than the catalogueState combinations, discount types, guest accounts
Rule Builder and Flow Builder logicQuick to write, slow to testCondition types, how exactly prices must match
Other plugins and integrationsCode for each oneWhat the count turns up
StorefrontDays to months, usually the largest partPage scope, CMS, designs, team

A proof of concept answers most of this for your own shop

  • Catalogue: every variant, price, tax category and collection matches Shopware's APIs.
  • Customers and a sample of orders: customers log in with their old passwords; totals match the invoices.
  • One rule price, as a price strategy: matches Shopware's Store API per customer group and quantity.
  • One storefront page with cart: a guest adds a variant at the right price.
  • One payment provider in test mode: a test order is placed and paid.
Cutover

Rehearse locally, cut over from a dump

The reference migration has not gone live and ran on one machine. For a real shop, Kevin would not open both production databases. Rehearse locally instead: restore a Shopware dump, run a local Vendure with the production configuration and version, and verify. For the final run, put Shopware into maintenance mode, take a fresh dump, let the local Vendure write its assets straight to the production S3 bucket, then restore the Vendure database dump on the server. Rehearse into a separate bucket, or every run leaves a full set of images behind.

The reference migration connected no ERP, PIM, payment or shipping provider. Each one is a workstream with its own owner.

Before go-live

Six risks to check

Stale source indexes

Run bin/console dal:refresh:index twice before the final extract, and check it. In the demo shop the listing index was stale and almost no product had an SEO URL.

Redirects

Build them from Shopware's canonical SEO URLs, after the index rebuild.

Background jobs

Measure the queue in a rehearsal, not on go-live day.

Prices

If rule prices are simplified instead of rebuilt, announce every price change before go-live.

Customer logins

Passwords carry over. Accounts bound to different sales channels can share an email: decide which one Vendure keeps. Older legacy_password hashes need a reset.

Order history

Imported orders reserve no stock and send no mail. Refunds need a payment handler with the old method's code. Keep the invoice PDFs as the legal record.

Appendix A

The two schemas side by side

Shopware 6.7 defines 219 core entity tables; most need no Vendure counterpart. 44 are *_translation tables and about 45 hold platform infrastructure a migration does not move. The tables below map what carries merchant data: Shopware as tables, because an extract reads tables, and Vendure as entities, because the Admin API writes entities.

Five rules apply to every Shopware query, whatever the table:

  1. Filter on the live version (version_id = 0fa91ce3e96a4bc2be4bd9ce752c3425).
  2. Resolve inheritance before reading a value.
  3. Resolve translations with Shopware's own fallback order.
  4. Parse the JSON columns, because that is where prices and payloads live.
  5. Keep the BINARY(16) IDs as the identity, not SKU or email.

Appendix B

How the reference migration was built

The migrator is a Node.js ES module project with mysql2 as its only dependency. Each stage reads the previous stage's files from a snapshot folder and writes its own, so any stage can run again without the ones before it. The code is at github.com/SpaceParrots/shopware-vendure-migrator (MIT licence); the excerpts below come from it, some shortened (column lists, error messages, and lines marked with an ellipsis).

Appendix C

Rule prices as a Vendure price strategy

Sketch, not run. The code in this appendix type-checks against @vendure/core 3.7.3. It is not registered in a Vendure server, has never run, and has no tests. Several condition evaluators throw not implemented. It shows the parts an implementation needs.

Kevin Mattutat
Written by

Kevin Mattutat

Founder and developer, SpaceParrots

Kevin Mattutat runs SpaceParrots, a software company in Berlin that builds commerce backends on Vendure for retail clients. He has been a Shopware Extension Partner since 2020 and sells a statistics extension in the Shopware Store. For this guide he ran a reference migration of Shopware's demo shop into Vendure, catalogue, customers and orders, and published the migrator, including the parts that did not migrate as data.

The author's views are their own and may not reflect the views of Vendure.

Next step

Planning the move?

Bring your Rule Builder rules, your Flow Builder automations and the plugins your Shopware build made you write. A partner, or our own engineers alongside your team, will sort them into what moves as data and what gets rebuilt as code before anyone writes code.