Guides

Why a plain WooCommerce CSV export doesn't import cleanly into Shopify

WooCommerce exports one shape of product data and Shopify reads another. What actually breaks on a manual CSV import, and when a spreadsheet is still fine.

Published September 14, 2026

Shopify’s documentation points you at a CSV file, and WooCommerce has an exporter built in. Products > All Products > Export, pick your columns, download. It looks like the two halves of a migration.

They are not. The file WooCommerce writes and the file Shopify reads describe products in different shapes, and closing the gap is not a matter of renaming a few headers. Below is what actually breaks, roughly in the order you will hit it.

The column names don’t line up

WooCommerce exports Name, Regular price, Sale price, Images, Stock and Categories. Shopify’s product CSV wants Title, Price, Compare-at price, Product image URL, Inventory quantity and Collection.

Matching those up is tedious but doable, and Shopify is more forgiving here than people expect. Its documentation states it still accepts older header names like Handle and Image Src for backward compatibility, so a template from an old forum thread will not fail on the header row alone.

The columns that look like a match and aren’t are the actual problem.

One exception is worth knowing because it kills the whole file. Shopify replaced the singular Barcode column with Barcodes, and its docs are blunt about the overlap: “Your CSV file can’t have both the Barcode column and the Barcodes column. A file that has both columns fails to import.” If you merged an old export with a new one in a spreadsheet, that is where the import dies.

Regular price and Sale price go the wrong way round

This is the mistake we see most, and it is silent.

WooCommerce has Regular price and Sale price. Shopify has Price and Compare-at price. The obvious mapping is regular to price, sale to compare-at. That is backwards. In Shopify, Price is what the customer actually pays and Compare-at price is the crossed-out number beside it.

Map it the obvious way and your store goes live charging the pre-sale price, with the discount shown as the old price. Nothing errors. The import succeeds, the products look right in the admin, and you find out from a customer.

Variable products arrive as two kinds of row

WooCommerce exports a variable product as a parent row with Type set to variable, followed by one row per variation with Type set to variation and a Parent column containing something like id:1482.

Shopify has no parent row. A product with four variants is four rows sharing the same URL handle, where the first row carries the title, description and vendor, and the rows after it leave those cells empty and fill in only the variant fields.

So the parent row becomes a standalone product with no variants, and you get a duplicate. The variation rows lose their link to it, because id:1482 means nothing to Shopify. Neither file contains a handle at all, so you generate those yourself and make them match across every row of a product.

Handles are fussier than they look. Ours logs Product {name} doesn't have a handle, creating handle 'draft-{id}' when a WooCommerce product has no usable slug, and it refuses products outright with Skipping product ID {id} because handle contains a double quote. A spreadsheet will not warn you about either.

Empty attribute values break variants, and WooCommerce has plenty of them

WooCommerce lets a variation leave an attribute unset, which is how “any size” variations work. Shopify has no equivalent. Every variant needs a value in Option1 value.

We hit this often enough that we handle it specifically. When a WooCommerce variation comes back with nothing in its attributes, the Shopify Admin API rejects the product with:

Variable $input of type ProductSetInput! was provided invalid value for variants (Expected value to not be null)

We catch that string and show merchants “One or more variants have an empty attribute” instead, because the raw version helps nobody. Where the variation count matches the parent’s attribute combinations we fill the gap and log it honestly, so the job log reads Created fallback variants using option 'Option' with generated values 'Option #1', 'Option #2', etc. because WooCommerce variation attributes were empty.

Through a CSV, those rows just have a blank cell. You will not know which products are affected until the import rejects them or, worse, doesn’t.

Attributes are doing two jobs and Shopify only accepts one

WooCommerce writes attributes as Attribute 1 name, Attribute 1 value(s), Attribute 1 visible and Attribute 1 global, repeating for as many as the product has.

Some of those drive variations. Others are specification data sitting in the Additional Information tab or feeding a filter widget: material, wattage, country of origin, warranty length.

Shopify allows three options per product. A product with six attributes where two of them drive variations has four attributes with nowhere to go.

Metafields are the right home, and the product CSV does carry them. The column header has to name a metafield definition that already exists in your admin, written as Fabric (product.metafields.shopify.fabric) or just product.metafields.shopify.fabric. Create the definitions in Settings > Custom data first.

There is a hard limit here that catches people out. Variant metafields are not supported by the product CSV at all. Shopify’s own documentation says so and points you at the variant bulk editor instead. If your WooCommerce variations carry their own data, no product CSV will move it.

Your categories collapse into one collection each

WooCommerce products sit in as many categories as you like. The Shopify product CSV has a Collection column, and its documentation is direct about the ceiling: “You can add a product to only one collection.”

Apart from metafield columns, the Collection column is the only column Shopify lets you add to the file. Everything else has to be a recognised header or the format breaks.

So a product filed under Outdoor, Jackets and Sale in WooCommerce keeps exactly one of those through a CSV. Rebuilding the rest is manual work after the fact, or it is tags, which is a different structure and a different theme setup.

Images: one cell, many URLs, and a fetch your server has to survive

WooCommerce puts every image for a product in a single Images cell as a comma-separated list. Shopify wants one row per image, each with its own Product image URL and Image position, plus a separate Variant image URL where a variant has its own photo. Up to 250 images per product.

Splitting that is scriptable. The next part is not.

Shopify downloads those URLs from your WooCommerce server during the import, and it fetches them faster than most WooCommerce hosting will serve them. On a catalogue of any size some images do not arrive, and the import does not stop to tell you which ones. We hit this early on and built handling for it into WooCommerce Importer, which is why the image count matches at the end. Through a CSV you are relying on your old server keeping up.

Shopify also requires the URLs be publicly reachable with no password protection, and rejects filenames carrying _thumb, _small or _medium suffixes. Worth checking against your uploads folder before you find out mid-import.

The images inside your descriptions are a separate problem

Every <img> tag inside a product description still points at yourstore.com/wp-content/uploads/.... The CSV copies that HTML across untouched, so it works perfectly in testing, because the old site is still up.

Then you cancel the WooCommerce hosting and every size chart, diagram and lifestyle shot inside a description breaks at once. WooCommerce Importer rewrites those into Shopify-hosted files during the import. Going the CSV route, budget the time to find and rehost them yourself before the old server goes dark.

Blank cells are instructions, not omissions

This one deserves its own warning because it bites on the second import rather than the first.

If you tick “Overwrite products with matching handles” and a non-required column in your file is empty, Shopify writes that blank over your existing value. Vendor set to John’s Apparel in Shopify, vendor column empty in your file, vendor is now empty.

It goes further. Include a SKU column without Option1 name and Option1 value, and Shopify deletes the product’s variant options. Its docs put it plainly: if those columns are missing, “a new default variant is created and existing variants are deleted.”

Editing a 6,000-row file in Excel and deleting a column you thought was noise is enough to do this.

What isn’t in the file at all

WooCommerce’s built-in exporter does products. That is the whole scope.

No orders. No customers. No coupons, reviews or URL redirects. Orders and customers need a separate plugin, and Shopify’s product CSV importer will not read them anyway, so that is a different tool and a different format again.

Redirects matter more than people expect. Your WooCommerce URL was probably /product/blue-widget/ and your Shopify URL will be /products/blue-widget. Different path, and no CSV creates the redirect.

Product types that have no Shopify equivalent

The WooCommerce exporter happily exports grouped products, subscriptions, bookings and bundles. They land in the file as ordinary rows. Shopify has no concept matching any of them, so you end up with a product record that looks plausible and behaves like nothing.

We chose to be loud about this instead. WooCommerce Importer handles simple and variable, treats external and redq_rental as simple, and writes Skipping product {id} with type {type} to the job log for everything else, so you can see exactly what did not come across. If you sell through WooCommerce Subscriptions, you are moving those by hand whichever tool you pick.

When the CSV route is genuinely fine

We are not going to tell you never to use a spreadsheet. Row count is not what decides this. Plenty of people work comfortably in a 50,000-row sheet, and plenty of people would rather not open one with 200 rows.

What decides it is the shape of your data. If your products are all simple, every attribute is spec data rather than a variation driver, each product belongs in one category, your images are already somewhere public and stable, and you are not moving orders, then a CSV is a reasonable afternoon. You will understand your own data better afterwards. Every extra “no” in that list is another pass through the file.

A CSV does give you one thing we do not. Your data sits in a spreadsheet before it reaches Shopify, so you can correct it there. If your catalogue genuinely needs cleaning up and you would rather do that in a sheet than in Shopify afterwards, that is a fair reason to take the manual route.

What we built instead reads your store over the WooCommerce REST API in its own structure rather than a flattened export, and writes it into Shopify’s. No file in the middle means no mapping step, no blank cell overwriting a real value and nothing for a spreadsheet editor to mangle.

Try a free demo import with WooCommerce Importer and check the result against your own products before paying anything.

Plan your WooCommerce to Shopify migration

Migrating more than 90 days from now? We'll set up a free Shopify client transfer store for you — a real Shopify environment where you can configure everything and run test migrations at your own pace. You only start paying for Shopify when you're ready to go live and we hand the store over to you. No obligation: if you change your mind, just let us know and we'll close the store.