Migrate from Shopware to Vendure: a partner-written guide | Vendure

Written by Kevin Mattutat from SpaceParrots

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.

Compare Vendure and Shopware See pricing

Migration guide

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.

Is this guide for your shop?

A good fit

Not a good fit

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.

Count before you map

Count Why it matters
Products with and without variants Either way, each becomes one Vendure product with at least one variant
Variants that inherit from their parent Every inherited field and association has to be resolved before loading
Rule prices, per rule and condition type One piece of code per condition type
Customer rows per email, and guest rows Shopware creates a row per guest checkout; Vendure keeps one customer per email
Order, delivery and payment state combinations Each one that occurs needs a Vendure state
Order line items per type Only product lines become Vendure order lines
Canonical SEO URLs The redirect map is only as complete as this table
Shopping Experiences layouts Each one is rebuilt in the storefront or a CMS
Settings overridden per sales channel The 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. Extract
    Every table in one read-only transaction, draft versions skipped.
  2. Transform
    Resolve inheritance, merge customers by email, map each order's states.
  3. Load
    The catalogue through the Admin API; customers and orders through a script inside Vendure.
  4. Verify
    Vendure against the target model, and customers and orders against Shopware's database.
  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.

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.

Why the script saves entities directly

The order service would reprice imported orders, and the customer service would hash a new password and publish a registration event. So the loader uses Vendure's services for customer groups and shipping methods, and saves orders and customers as entities through the database connection.

// Type-safe GraphQL via gql.tada
const productQuery = graphql(`
 query Product($slug: String!) {
 product(slug: $slug) {
 id
 name
 priceWithTax
 stockLevel
 }
 }
`)

// Server component — Next.js 16 RSC
export async function ProductCard({ slug }: { slug: string }) {
 const { product } = await query(productQuery, { slug })
 return (
 <Card>
 <CardTitle>{product.name}</CardTitle>
 <Price value={product.priceWithTax} />
 </Card>
 )
}

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.

What ported as data, and what got rebuilt

Ported or transformed as data

Rebuilt, replaced or retired

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.

Rules, in the order Shopware checks them priority DESC, id ASC

  1. P100 0a…Billing country is USDoes not match
  2. P100 3f…Cart amount above thresholdMatches, no prices here
  3. P100 8c…All customersFirst with prices: wins
  4. P10 1b…Day of week is SundayCheaper, never reached

Within the winning rule, the tier covering the line quantity applies. No matching rule with prices means the base price.

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.

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

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.

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.