Shopify ⇄ WooCommerce Product CSV Migrator

The two platforms do not just name columns differently — they store products differently. This converts the structure, translates the values it can prove, and tells you exactly what it could not carry over.

🔒 Your catalogue never leaves your browser.

Read locally, never uploaded.

The three things a column rename cannot do

1. Variants are a different shape

Shopify keeps a product together by repeating its Handle down consecutive rows. WooCommerce has no grouping column: the parent is one row of type variable and every variant is its own row of type variation pointing back through Parent. Since Shopify has no product-level SKU to point at, the parent here is given the handle as its SKU — stated in the conversion list rather than done silently.

2. Sale prices are stored the other way round

Shopify puts the current price in Variant Price and the crossed-out one in Variant Compare At Price. WooCommerce puts the crossed-out price in Regular price and the current one in Sale price. Rename the columns straight across and every discount in the catalogue silently disappears — the import succeeds, and everything goes back to full price.

3. Images live in different places

Shopify writes one row per image; WooCommerce expects them comma-separated in a single cell. Either way the URLs point at the source store, so the new store depends on the old one staying online until you re-host them.

Everything this conversion does (21 documented rules)

Structure: how products and variants are reshaped

MIG-S01 Handle groups become a variable product plus variation rows

Shopify keeps a product together by repeating its Handle down consecutive rows. WooCommerce has no such grouping: the parent is one row of Type "variable" and each variant is its own row of Type "variation" pointing back with Parent. The whole file has to be restructured, not renamed.

Official documentation ↗

MIG-S02 Image rows collapse into one comma-separated Images cell

Shopify gives each image its own row (Image Src plus Image Position, with every other column empty). WooCommerce expects all image URLs in a single Images cell on the product row, in order. The conversion sorts by Image Position and joins them.

Official documentation ↗

MIG-S03 Option columns become attribute columns

Option1/2/3 Name and Value become Attribute N name and Attribute N value(s). The parent row carries the full set of values joined with " | "; each variation row carries the single value it is pinned to.

Official documentation ↗

MIG-S04 The variable parent gets a SKU derived from its handle

WooCommerce links a variation to its parent through the Parent column, which accepts a SKU or "id:123" — and an ID only exists after the import. Shopify has no product-level SKU at all (only variants carry one), so the parent row would have nothing to point at. The conversion therefore gives the parent the handle as its SKU: it is unique, it already identifies the product, and it is stated here rather than invented silently.

Fix: Keep it, or search-and-replace the parent SKUs for your own scheme before importing — each variation's Parent column must match whatever you choose.

Official documentation ↗

MIG-S06 The derived parent SKU collided with a variant SKU

A variant in this file already uses the handle as its SKU, so using it for the parent as well would produce two rows with the same SKU — and WooCommerce rejects the second one. The parent falls back to the handle plus "-parent".

Fix: Rename either SKU in the source file if the generated one does not suit you.

Official documentation ↗

MIG-S05 Single-variant products become simple products

A Shopify product whose only option is the default "Title / Default Title" is not a variable product — it converts to a plain simple product with no attributes, which is what it actually is.

Official documentation ↗

Values: what gets translated, and how

MIG-V01 Sale prices are the other way round on the two platforms

Shopify writes the current price in Variant Price and the crossed-out price in Variant Compare At Price. WooCommerce writes the crossed-out price in Regular price and the current one in Sale price. A straight column rename therefore erases every discount: the file imports cleanly and every product quietly returns to full price.

Fix: This conversion swaps them: when Compare At is higher, Regular price takes Compare At and Sale price takes the current price.

Official documentation ↗

MIG-V02 Weights convert between grams and the store unit

Shopify exports Variant Grams. WooCommerce takes a bare number whose unit lives in the header ("Weight (kg)") and comes from the store settings. The conversion divides by 1000 and names the unit in the header so the number cannot be read as grams by mistake.

Official documentation ↗

MIG-V03 Booleans and status values are re-encoded

Shopify writes TRUE/FALSE; WooCommerce reads 1/0 and treats anything else as 0. Shopify Status (active / draft / archived) maps onto WooCommerce Published as 1 / -1 / 0 — archived becomes private rather than deleted, because a conversion must never remove products.

Official documentation ↗

MIG-V04 Inventory policy becomes the backorders setting

Shopify's Variant Inventory Policy "continue" means "keep selling when out of stock", which is WooCommerce's Backorders allowed? = 1; "deny" is 0.

Official documentation ↗

MIG-V05 Taxable flag becomes tax status

Variant Taxable TRUE/FALSE becomes Tax status taxable/none. Tax class is left empty so the target store's default applies — guessing a tax class is the kind of quiet mistake that only surfaces in an invoice.

Official documentation ↗

MIG-V06 Barcodes are carried, but the importer will not map that column by itself

Variant Barcode holds the GTIN. WooCommerce has a native GTIN field, but it is not part of the default CSV mapping, so the column arrives and is ignored unless you map it on the import screen. The conversion carries the values rather than dropping them, and says so here.

Fix: On the WooCommerce import screen, map the "GTIN, UPC, EAN, or ISBN" column by hand.

Official documentation ↗

MIG-V07 Handles become slugs, names become handles

Going the other way, WooCommerce has no Handle column: the conversion derives one from the product name (lowercase, hyphenated) and keeps it identical across a product and its variations, because that is what groups them in Shopify.

Official documentation ↗

Columns with no equivalent

MIG-U01 Column has no equivalent in the target platform

The target platform has no core column for this data. It is listed rather than guessed at: writing it into an approximately similar column is how migrations end up with vendor names in the category tree.

Fix: Decide per column: keep it in a custom field ("meta:my_field" imports into WooCommerce), map it manually after import, or accept the loss.

Official documentation ↗

MIG-U02 SEO fields belong to a plugin, not to WooCommerce core

SEO Title and SEO Description are Shopify core fields but WooCommerce stores them in whichever SEO plugin you use (Yoast, Rank Math, SEOPress), each with its own meta keys. There is no single correct target, so the conversion refuses to pick one.

Fix: Import the products first, then use your SEO plugin's own import to attach these fields.

Official documentation ↗

MIG-U03 Per-market pricing columns are dropped

Shopify exports one column per market ("Price / International"). WooCommerce has a single price per product; multi-currency is a plugin concern. Those columns are dropped rather than flattened onto the base price.

Official documentation ↗

Decisions only you can make

MIG-W01 Shopify allows only three options per product

WooCommerce products can carry any number of attributes; Shopify caps variant options at three. Attributes beyond the third cannot be represented as options and would silently disappear.

Fix: Merge or drop attributes down to three before converting, or keep the extra ones as plain product information.

Official documentation ↗

MIG-W02 Product type and category do not correspond

Shopify has Type and Product Category (a fixed taxonomy); WooCommerce has Categories, a store-specific tree. They are different concepts, so mapping one onto the other is a decision about your catalogue, not a conversion.

Fix: Build the WooCommerce category tree first, then fill the Categories column — the format is "Parent > Child", comma separated.

Official documentation ↗

MIG-W03 Images are referenced by URL, not copied

Both platforms take image URLs, so the converted file points at the original store's CDN. The import works, but the new store then depends on the old store staying online — and a Shopify store that is closed takes its images with it.

Fix: Download the images and re-upload them to the new store, or run the import while the old store is still live and re-point the images afterwards.

Official documentation ↗

MIG-W04 Body (HTML) carries platform-specific markup

Shopify descriptions often contain classes, inline styles and shortcodes that mean something only in the old theme. WooCommerce imports the HTML verbatim, so the markup arrives intact and renders differently.

Fix: Spot-check a few descriptions after import; the description HTML checker lists the tags each platform strips.

Official documentation ↗

MIG-W05 The file does not look like either platform export

Neither the Shopify column set (Handle, Title, Variant Price…) nor the WooCommerce one (Type, SKU, Regular price…) is recognisable in the header row, so there is no way to know what the values mean.

Fix: Export again from the source platform without renaming columns, or state the direction explicitly.

Official documentation ↗