Key takeaways

  • Handle is the grouping key. Rows sharing a Handle become one product with multiple variants.
  • Only the first row of a Handle group carries product-level fields. Repeating them on later rows causes odd results.
  • Blank is not the same as absent. Shopify applies defaults, and a blank price imports as 0.00.
  • Three options and 100 variants per product, hard limits, no exceptions.

Shopify's product CSV is unusual: it is not one row per product, and it is not one row per variant either. It is one row per thing to say about a product, grouped by a slug in the first column.

Once that clicks, the rest of the format is straightforward. Until it does, the import produces mystifying results.

The Handle rule, which explains everything else

Handle is the URL slug and the grouping key at the same time. Every consecutive row with the same Handle belongs to the same product.

Within a group:

  • The first row carries product-level fields: Title, Body, Vendor, Type, Tags, Published, plus its own variant and first image.
  • Subsequent rows carry additional variants, additional images, or both, and leave the product-level fields blank.

A t-shirt in three sizes with four photos looks like this:

Handle,Title,Option1 Name,Option1 Value,Variant SKU,Variant Price,Image Src,Image Position
cotton-tee,Cotton Tee,Size,Small,TEE-S,24.99,https://cdn.example/1.jpg,1
cotton-tee,,Size,Medium,TEE-M,24.99,https://cdn.example/2.jpg,2
cotton-tee,,Size,Large,TEE-L,24.99,https://cdn.example/3.jpg,3
cotton-tee,,,,,,https://cdn.example/4.jpg,4

Three variants, four images, one product. The last row is images only, so it leaves every variant column empty.

Repeating the Title on variant rows is the classic mistake. It usually still imports, but combined with any inconsistency elsewhere it is what turns one product with three variants into three products with one variant each.

Product-level columns

ColumnWhat it doesIf blank
HandleURL slug and grouping key. Lowercase, hyphens, no spaces.Row is skipped
TitleProduct name shown to customers.Treated as a continuation row
Body (HTML)Description. Accepts HTML. Wrap in quotes if it contains commas.Empty description
VendorBrand. Used by automated collections and filters.Store name
Product CategoryShopify's standard taxonomy. Feeds Google Shopping.Uncategorised
TypeYour own free-text product type.Empty
TagsComma separated inside one quoted cell. Drives automated collections.No tags
PublishedTRUE or FALSE. Visible on the online store channel.TRUE
Statusactive, draft or archived.active
Gift CardTRUE only for gift card products.FALSE

Import as draft first. Set Status to draft and Published to FALSE on your first run. You get to inspect everything in the admin before customers can see it, and flipping the whole set to active afterwards is one bulk action.

Option columns

Options are the choices a customer makes. You get three, named in pairs.

ColumnNotes
Option1 NameThe attribute label, for example Size. Must repeat identically on every variant row in the group.
Option1 ValueThis variant's value, for example Medium.
Option2 Name / ValueSecond option, for example Colour.
Option3 Name / ValueThird and final option.

For a product with no choices at all, leave all six blank. Shopify creates a single default variant automatically. Do not invent an option called "Default Title" yourself.

The hard limits: 3 option names and 100 total variant combinations per product. These are platform constraints, not import constraints, and no CSV trick works around them. If you are coming from WooCommerce, this is the migration issue most likely to cost you a day, and it is covered in detail in the WooCommerce to Shopify migration guide.

Variant columns

ColumnWhat it doesIf blank
Variant SKUYour stock code. Also the matching key for future updates.Empty, and updates become unreliable
Variant PriceSelling price. Numbers only, no currency symbol.0.00
Variant Compare At PriceThe struck-through "was" price. Must exceed Variant Price to display.No sale badge
Variant GramsWeight in grams, always, regardless of your display unit.0
Variant Inventory Trackershopify to track stock, blank to sell without tracking.Untracked, always in stock
Variant Inventory QtyUnits on hand at the default location.0
Variant Inventory Policydeny or continue selling when out of stock.deny
Variant Fulfillment Servicemanual unless using a third-party fulfilment app.manual
Variant Requires ShippingFALSE for digital products.TRUE
Variant TaxableWhether tax applies.TRUE
Variant BarcodeISBN, UPC or GTIN. Needed for Google Shopping.Empty

Two defaults that bite. A blank Variant Price becomes 0.00 and the product goes live as free. A blank Variant Inventory Tracker means Shopify never decrements stock and you oversell. Neither produces an import error.

Image columns

ColumnNotes
Image SrcAbsolute, publicly reachable URL. Shopify fetches it at import and copies it to its own CDN.
Image Position1 is the featured image. Increment for the rest.
Image Alt TextAccessibility and image SEO. Genuinely worth filling in.
Variant ImageThe image shown when this specific variant is selected. Must be one of the URLs already in the group.

The URLs must be reachable by Shopify's servers at the moment of import: public, no authentication, no hotlink protection, no referer check. If your source CDN blocks unknown referers, every image is skipped without an error message.

Get a valid Shopify CSV in one step

Scrapify reads any WooCommerce or Shopify store and writes the Shopify format directly, with Handle grouping, variant rows and absolute image URLs already correct.

Build a Shopify CSV Get it done for you

SEO and remaining columns

  • SEO Title and SEO Description: meta tags. Left blank, Shopify falls back to the product title and the first part of the description, which is worse but not broken.
  • Google Shopping / *: legacy columns Shopify largely superseded with Product Category. Modern setups can ignore them.
  • Cost per item: your buy price. Never shown to customers, powers margin reporting.
  • Included / [Country], Price / [Country]: only appear if you have Shopify Markets configured.

File-level rules that cause silent failures

  • UTF-8 encoding. Save as CSV UTF-8. Excel's plain "CSV" option produces Windows-1252 and turns accented characters into question marks.
  • Comma delimited. Not semicolons. European Excel locales default to semicolons and Shopify will not recognise the file.
  • Quote any cell containing a comma. Especially Tags and Body (HTML).
  • Escape quotes by doubling them. A 15" laptop is written 15"" laptop inside a quoted cell.
  • 15MB maximum. Split larger files, keeping every row of a Handle group together in the same file.
  • Keep Handle groups contiguous. Sort by Handle. Splitting a group across the file confuses the grouping.

Using the CSV to update existing products

Re-importing with a Handle that already exists updates that product, provided you tick "Overwrite any current products that have the same handle". Without that box, Shopify skips the row entirely.

The gotcha is that overwrite is per row, not per field: any column you omit from the file is treated as blank and wiped. If you are updating prices only, include Handle, the option columns, Variant SKU and Variant Price and nothing else, or you will erase descriptions and images across the catalogue.

Test on a two-product file first, always.

Frequently asked questions

What does the Handle column do in a Shopify CSV?
Handle is the unique URL slug and the grouping key. Every row sharing the same Handle is treated as one product, with the first row carrying the product-level fields and later rows carrying additional variants and images. Get Handle wrong and you get duplicate products instead of variants.
Which Shopify CSV columns are actually required?
Strictly, only Handle and Title for a new product. In practice you also want Variant Price, Variant Inventory Qty, Variant SKU, Status and Published, because the defaults Shopify applies when those are blank are rarely what you want. A blank Variant Price imports as 0.00.
How do I add multiple images to one product in a Shopify CSV?
Add extra rows with the same Handle, leave every column blank except Handle, Image Src and Image Position, and increment Image Position. Do not repeat the Title or price on those rows, or Shopify may interpret them as variant rows.
Why did my Shopify import create duplicate products?
Almost always an inconsistent Handle. Handles are case sensitive in practice and must not contain spaces or special characters. If your source data produced premium-tee for one row and Premium-Tee for the next, Shopify creates two products.

Keep reading

Migration How to Migrate from WooCommerce to Shopify Without Losing Products WooCommerce The WooCommerce Product CSV Schema Explained, Field by Field Product Data The Shopify products.json Endpoint: What It Is and What It Returns