Migration
There is no official Shopware migration profile for WooCommerce. This guide covers what to move with which tool, how customers keep their WordPress passwords, and how your URLs survive the switch.
Shopware's Migration Assistant has no WooCommerce profile, so you assemble the move yourself. The catalogue can come across with a Store extension or the two REST APIs. Customers and orders need a script. Passwords survive with a small legacy encoder that understands WordPress hashes, and old URLs need a 301 map in the web server, because Shopware core has no redirect manager.
Checked against: Shopware 6.7.15.1, Migration Assistant 19.0.0 and WordPress 6.8.0 source, October 2026.
Moving from Shopware 5 has an official, free tool. Moving from WooCommerce does not. Migration Assistant 19.0.0 ships profiles for Shopware 5.4 to 5.7 and Shopware 6, and Magento lives in a separate Shopware extension. WooCommerce and Shopify have neither.
So you decide per data type how it moves, and the three things that hurt when they go wrong are customer passwords, order history and URLs.
Categories, products, variations, attributes, manufacturers and images. A Store extension or a script over both REST APIs.
Accounts and addresses through the API, password hashes from the database. A server-side import, see Step 3.
History for customer accounts and accounting. Always a script, and the part to scope first.
Pages and posts are rebuilt in Shopping Experiences. Every old URL gets a 301 target.
For the catalogue, one Store extension, "WordPress/WooCommerce To Shopware 6", imports categories, products, variants, properties, manufacturers and images. Its listing does not mention customers, orders, passwords or SEO URLs, so plan those separately whichever way the catalogue moves.
For a script, the WooCommerce REST API (/wp-json/wc/v3/) exports products, variations, customers and orders, and Shopware's Admin API sync endpoint (/api/_action/sync) writes them in batches.
Shopware 6 can check a password hash from another system. Each customer has a legacyEncoder and a legacyPassword field. On login, Shopware asks the encoder with that name whether the password matches the old hash, and if it does, replaces it with a native hash. After one login the customer is on Shopware's own hashing.
Core ships two encoders, Md5 and Sha256, for Shopware 5. WordPress needs its own, and since WordPress 6.8 it has to handle two formats: the older phpass hashes starting with $P$, and the bcrypt-based ones starting with $wp$2y$. WordPress only rehashes on login, so one export usually holds both.
This encoder mirrors wp_check_password() from WordPress 6.8.0 branch for branch. For the phpass branch it uses the PasswordHash class WordPress ships in wp-includes/class-phpass.php, copied into your plugin and loaded through its Composer classmap. The one branch it leaves out is WordPress's own $wp_hasher override, which a migration does not need.
<?php declare(strict_types=1);
namespace Acme\Migration\Password;
use Shopware\Core\Checkout\Customer\Password\LegacyEncoder\LegacyEncoderInterface;
class WordPressEncoder implements LegacyEncoderInterface
{
public function getName(): string
{
return 'WordPress';
}
public function isPasswordValid(#[\SensitiveParameter] string $password, string $hash): bool
{
if (\strlen($hash) <= 32) {
return hash_equals($hash, md5($password));
}
if (\strlen($password) > 4096) {
return false;
}
if (str_starts_with($hash, '$wp')) {
$prehash = base64_encode(hash_hmac('sha384', $password, 'wp-sha384', true));
return password_verify($prehash, substr($hash, 3));
}
if (str_starts_with($hash, '$P$')) {
return (new \PasswordHash(8, true))->CheckPassword($password, $hash);
}
return password_verify($password, $hash);
}
}
Register it as a service with the tag shopware.legacy_encoder. On import, set legacyEncoder to WordPress and legacyPassword to the user_pass value from the wp_users table. The REST API never returns hashes, so they come from the database.
Important: both legacy fields drop the ApiAware flag in 6.7.15.1, so the Admin API does not expose them. Write customers from a console command inside the same plugin, through the customer repository. An import that runs only over HTTP cannot carry the hashes.
On an English install, WooCommerce product URLs default to /product/slug/, categories to /product-category/slug/ and tags to /product-tag/slug/. Other languages translate these bases. Shop owners can change these bases, for example to /shop/slug/ or /shop/hoodies/slug/, and stores on plain permalinks use /?product=ID. Take the real structure from a crawl of the live shop.
Shopware core has no manager for arbitrary redirects. Its one redirect setting sends a 301 from Shopware's own superseded SEO URLs to the current one, and it knows nothing about WooCommerce paths. So the map goes into the web server or CDN as 301 rules, or into a redirect extension.
The quickest map pairs each old product URL with its new one through the SKU. Crawl the old shop, export the new SEO URLs per SKU from Shopware, and join the two lists. Then test the map against staging with the loop from the Shopware 5 to 6 guide, which works for any source platform.
Order import is the part with no ready-made tool, and the part merchants underestimate. Decide what the history is for before scoping it:
Imported orders keep their old prices and taxes. Write them as finished orders, with their own number range, so they never collide with new ones.
$P$ and $wp$ hashes.
Written by Huzaifa Mustafa
More on moving to Shopware 6:
Send us your store URL and a rough count of products, customers and orders. We'll tell you what moves with a tool and what needs a script.