Key takeaways
- WooCommerce accepts more than Shopify allows, so the data itself rarely fights you. The apps do.
- Shopify's own export uses Shopify's column names. WooCommerce will not read it without conversion.
- Image import is the slow step, because WooCommerce downloads every file during the request.
- Inventory every paid Shopify app before you cancel. Reviews, subscriptions and loyalty data live inside them.
Leaving Shopify is usually a cost decision. Transaction fees, app subscriptions and per-seat pricing add up, and at a certain revenue WooCommerce plus decent hosting is dramatically cheaper.
The good news is that the data direction is the easy one. WooCommerce imposes no variant ceiling, allows unlimited attributes, and has a nested category tree, so nothing has to be collapsed or discarded. The difficulty moves elsewhere.
Before you export anything
Do this audit first, because it determines whether the project is two days or two months.
- List every paid app and what it holds. Reviews, subscriptions, loyalty points, bundles, wishlist data. None of it is in the product export.
- Note your theme customisations. Liquid templates do not port. Budget for a WooCommerce theme build or a close-enough starting point.
- Check your payment gateway. Shopify Payments does not exist off Shopify. You need a new merchant account, and stored card tokens do not transfer.
- Export your URL list from Search Console. Do it now while the store is live and Google still reports on it.
Subscriptions are a hard stop. Recurring payment authorisations belong to Shopify's gateway and cannot be handed to WooCommerce Subscriptions. Every subscriber re-enters their payment details or churns. If subscriptions are a meaningful share of revenue, model that churn before committing.
Step 1: Export the catalogue
Two routes.
Shopify's built-in export
Products, then Export, then All products, CSV for Excel. You get Shopify's own schema: Handle, Title, Body (HTML), the Option columns, the Variant columns and Image Src.
It is complete and accurate for what it covers. It omits metafields entirely, which is where a lot of stores keep specifications, ingredients and care instructions.
Reading the storefront
The alternative is to read the store's public product data directly. This works without admin access, which matters if you are a developer quoting on a migration or the client cannot locate their login.
It also outputs in whatever schema you ask for. Tools like Scrapify read the store and write a WooCommerce-format CSV directly, which removes the conversion step entirely. If you want to understand what that data source contains, see the Shopify products.json endpoint explained.
Step 2: Convert the schema
If you used Shopify's export, the columns need renaming and the rows need restructuring. The core mapping:
| Shopify column | WooCommerce column | Notes |
|---|---|---|
| Handle | SKU or Parent reference | Woo groups by SKU and Parent, not by slug |
| Title | Name | Direct |
| Body (HTML) | Description | Direct, HTML preserved |
| Vendor | Attribute or Brand taxonomy | Woo has no native brand field before 9.4 |
| Type | Categories | Or a custom attribute |
| Tags | Tags | Comma separated in both |
| Option1 Name / Value | Attribute 1 name / value(s) | Set "in variations" to 1 |
| Variant SKU | SKU | On the variation row |
| Variant Price | Regular price | Direct |
| Variant Compare At Price | Regular price, with Variant Price as Sale price | The relationship inverts. See below. |
| Variant Inventory Qty | Stock | Also set "In stock?" to 1 |
| Variant Grams | Weight (kg) | Divide by 1000 |
| Image Src | Images | Woo takes all URLs in one comma-separated cell |
The sale price inversion catches everyone. Shopify stores the normal price in Variant Compare At Price and the current selling price in Variant Price. WooCommerce stores the normal price in Regular price and the discounted one in Sale price. Map them straight across and every discounted product becomes full price with a fake sale badge.
The row structure also changes. Shopify uses one row per variant grouped by Handle. WooCommerce uses one variable parent row plus one variation row per combination, with each variation pointing at the parent's SKU. The full field reference is in the WooCommerce product CSV schema explained.
Step 3: Import into WooCommerce
Products, All Products, Import. Upload the file, check the column mapping screen carefully, and run it.
Three settings that matter:
- Update existing products. Leave off for the first run. On for subsequent corrective runs.
- Batch size. Not exposed in the UI, but it is what your hosting limits govern. If the import dies, the file is too big for the server, not malformed.
- Column mapping. Anything WooCommerce cannot auto-detect defaults to "Do not import". Scroll the whole list.
The realistic constraint is images. WooCommerce downloads every image URL into the media library synchronously during the import request. A 2,000 product catalogue with six images each is 12,000 downloads, and shared hosting will not survive that in one request.
Split into batches of 200 to 500 products, or use an importer that fetches images in the background. Our free WordPress plugin does the latter: it reads a live feed and handles batching, retries and image fetching outside the request cycle. The setup is documented in the plugin guide.
Import without the CSV step
Turn any Shopify store into a live feed, then pull it into WooCommerce with our free plugin. Variations, images, categories and currency conversion included, with automatic re-sync.
Step 4: Make the variations actually appear
This is the most common "the import worked but the site is broken" report, and it always has the same cause.
For a variable product to show its options on the front end, three things must all be true:
- The parent row has
Typeset tovariable. - Every attribute on the parent has its visible flag and its in variations flag set to 1.
- Each variation row has
Typeset tovariationandParentset to the parent's SKU.
Miss the "in variations" flag and WooCommerce imports the attribute as a display-only specification. The variations exist in the database, they just have nothing to attach to, so the product renders as a plain item with a spec table and no add-to-cart options.
Fixing it after the fact means re-importing with the flag set, not editing products by hand.
Step 5: Redirects
Shopify URLs look like /products/premium-cotton-tee and /collections/t-shirts. WooCommerce defaults to /product/premium-cotton-tee and /product-category/t-shirts.
You have an unusual option here that most migrations do not: WooCommerce lets you change its permalink base. Set the product base to products and the category base to collections under Settings, Permalinks, and a large share of your URLs match without any redirect at all.
It feels like a hack. It saves hundreds of redirect rules and eliminates the risk of getting them wrong. Take the win.
For whatever still differs, add 301s with Redirection or at the server level, and verify with a header checker rather than by clicking, because a 302 looks identical in a browser and passes no authority.
Step 6: Replace the apps
Rough equivalents for the common ones:
| Shopify app | WooCommerce equivalent | Data transfer |
|---|---|---|
| Judge.me, Loox, Yotpo | Built-in reviews, or a reviews plugin | Export from the app, import as comments |
| Recharge, Bold Subscriptions | WooCommerce Subscriptions | Customer list only. Payments must be re-authorised. |
| Klaviyo | Klaviyo (it supports Woo) | Straightforward, reconnect the integration |
| Smile.io loyalty | A points plugin | Export balances, import as a one-off credit |
| Shogun, PageFly | A block or page builder | None. Rebuild the pages. |
Pre-launch checklist
- Product count matches Shopify exactly.
- Variable products show their dropdowns on the front end.
- Zero products without a featured image.
- Sale prices are the discounted figure, not the original.
- Media library count is roughly your expected image total, meaning nothing failed to download.
- Tax and shipping rules recreated and tested at checkout.
- A real test order placed, paid, refunded.
- Redirects returning 301, spot checked.
- Search Console property added for the new setup.
Keep the Shopify store on its cheapest plan for a month after cutover. It is a cheap insurance policy against the thing you did not think to check.