Key takeaways
- Variable products need a parent row plus one row per variation, linked by SKU.
- Attributes need three flags set correctly or the variations import but never display.
- Category hierarchy uses
>to nest and,to separate. Spaces matter. meta:columns write anywhere, with no validation. Powerful and unforgiving.
WooCommerce's importer is more flexible than Shopify's and correspondingly easier to get subtly wrong. It accepts almost any column order, auto-detects standard headers, and will happily write custom fields you invented. It also fails quietly when a flag is missing.
This is the field reference, with the rules that are not obvious from the column names.
Core product columns
| Column | Accepts | Notes |
|---|---|---|
ID | Integer | Existing post ID. Leave blank for new products. |
Type | simple, variable, variation, grouped, external | Also accepts modifiers: variable, virtual |
SKU | Text | The matching key for updates. Must be unique site-wide. |
Name | Text | Product title |
Published | 1, 0, -1 | 1 published, 0 private, -1 draft |
Is featured? | 1 or 0 | Featured product flag |
Visibility in catalog | visible, catalog, search, hidden | Where the product appears |
Short description | HTML | The excerpt, shown next to the price |
Description | HTML | The main tab content |
Regular price | Decimal | No currency symbol, no thousands separator |
Sale price | Decimal | Must be lower than regular price to apply |
Tax status | taxable, shipping, none | |
Tax class | Text | Blank means standard rate |
In stock? | 1, 0, backorder | Independent of the Stock number |
Stock | Integer | Only used when stock management is on |
Backorders allowed? | 1, 0, notify | |
Weight (kg) | Decimal | Unit follows your store settings despite the header |
Length/Width/Height (cm) | Decimal | Same caveat |
Categories | Text | See hierarchy rules below |
Tags | Comma separated | Created if they do not exist |
Images | Comma separated URLs | First is the featured image, rest are the gallery |
Parent | SKU or id:123 | Links a variation to its variable parent |
Position | Integer | Menu order for sorting |
Published uses -1 for draft, not 0. Zero means private, which is published but visible only to logged-in admins. Importing a test batch with 0 expecting drafts leaves you with products that are live in a sense you did not intend.
Category hierarchy
Nest with a greater-than sign, separate with commas:
Clothing > Shirts > Long Sleeve, Sale, New Arrivals
That assigns the product to three categories and creates the entire Clothing tree if it does not already exist. Only the deepest term in each chain is actually assigned; the ancestors are created as structure.
Two things that silently duplicate your taxonomy:
- Inconsistent spacing.
Clothing >ShirtsandClothing > Shirtsproduce different terms. Normalise before importing. - A comma inside a category name. "Shoes, Boots & Sandals" splits into three categories. Escape the cell or rename the category.
Attributes, and the three flags
Attribute columns come in numbered sets. For each attribute N:
| Column | Value | What it controls |
|---|---|---|
Attribute N name | Text, e.g. Size | The label |
Attribute N value(s) | Comma separated on parents, single value on variations | The options |
Attribute N visible | 1 or 0 | Shows in the Additional Information table |
Attribute N global | 1 or 0 | 1 uses a site-wide taxonomy, 0 is product-specific |
Attribute N default | Text | Pre-selected option on the product page |
The critical, invisible one is in variations. In the WooCommerce exporter's own output this appears as part of the attribute configuration rather than as a plainly named column, and it is the flag that tells WooCommerce this attribute is a customer choice rather than a specification.
Without it, variation rows import successfully and attach to nothing. The product renders as a simple item with a spec table and no dropdowns, which is exactly the symptom described in 12 WooCommerce import errors and how to fix them.
Structuring a variable product
A shirt in three sizes and two colours is seven rows: one parent, six variations.
Type,SKU,Name,Parent,Regular price,Attribute 1 name,Attribute 1 value(s),Attribute 1 visible,Attribute 1 global
variable,SHIRT-01,Oxford Shirt,,,"Size","Small, Medium, Large",1,1
variation,SHIRT-01-S-BLU,,SHIRT-01,49.99,"Size","Small",1,1
variation,SHIRT-01-M-BLU,,SHIRT-01,49.99,"Size","Medium",1,1
variation,SHIRT-01-L-BLU,,SHIRT-01,49.99,"Size","Large",1,1
Rules:
- The parent carries every possible value for each attribute, comma separated.
- Each variation carries one value per attribute.
Parenton the variation is the parent's SKU, orid:123if you are matching on post ID.- The parent's price columns are ignored. Price lives on variations.
- Variations do not need a
Name. WooCommerce derives it. - Order matters. The parent must appear before its variations in the file. Sort by SKU or Parent.
Let the format be someone else's problem
Scrapify writes WooCommerce-schema CSV directly from any store URL, with parent and variation rows, attribute flags and category hierarchy already correct.
The meta: escape hatch
Any column named meta:key writes its value to that post meta key. This is how plugin data and ACF fields survive an import.
meta:_yoast_wpseo_title
meta:_yoast_wpseo_metadesc
meta:_custom_ingredient_list
meta:_wc_cog_cost
It is completely unvalidated. A typo in the key does not error, it creates orphaned meta that nothing reads. Underscore-prefixed keys are hidden from the custom fields UI, so you cannot easily spot the mistake afterwards either.
The reliable way to get these right: export one existing product that already has the data, look at the exact key names WooCommerce emits, and copy them.
Downloadable and external products
| Column | Applies to | Notes |
|---|---|---|
Download limit | Downloadable | Blank means unlimited |
Download expiry days | Downloadable | Blank means never |
Download N name | Downloadable | Label shown to the customer |
Download N URL | Downloadable | Must be reachable at import time |
External URL | External/Affiliate | Where the button sends the customer |
Button text | External/Affiliate | Defaults to "Buy product" |
Grouped products | Grouped | Comma separated child SKUs or id: refs |
Upsells / Cross-sells | All | Comma separated SKUs. Targets must already exist. |
Upsells and cross-sells need two passes. They reference other products by SKU, and those products must exist when the row is processed. Import everything once, then re-import with "update existing products" ticked and the relationship columns populated.
File rules
- UTF-8, ideally with BOM. Without it, WooCommerce sometimes guesses Latin-1 and mangles accented characters.
- Comma delimiter. The importer does offer a delimiter setting, but sticking to commas avoids a class of problems.
- Standard header names. Order is irrelevant, spelling is not. Exact names skip the manual mapping step.
- Parents before variations. The one ordering rule that does exist.
- Absolute image URLs. Relative paths are silently skipped.
The safest way to produce a file WooCommerce will definitely accept is to export two or three existing products first and use that as your template. The importer and exporter share a schema, so anything the exporter emits is by definition importable.