Shopify Product Import Rescue Kit

Thank you for buying. Everything in the kit is on this page - you can read it here or download the zip.

Confirm your order

Paste the order number from your Gumroad receipt. It looks like a1b2c3d4-....

Download the kit (.zip)

1. Error code reference

Error messageLikely causeFix
Invalid variantOptions/title blankEvery variant needs a parent title and at least one option value
Duplicate handleTwo rows share a handleEither merge them as variants or give one a new one
Price is invalidText in price cellNumbers only, no currency symbol, no thousands separator
Leading zeros lostExcel stored SKU as numberFormat the SKU column as Text before saving
Row has wrong column countComma inside a descriptionQuote the whole field, or switch delimiter
Image not foundRelative pathAbsolute https:// URLs, one per line, no spaces
Variant already existsRe-importing same SKUDelete the product first or update by handle
Inventory not trackedTracking offSet tracker and quantity on every variant
Product type missingEmpty taxonomySet a product type; Shopify uses it for collections
Weight unit mismatchg vs kgMatch the unit to your store setting
Tags look like one tagComma inside quotesShopify splits on commas; use separate columns
BOM in first headerExcel UTF-8 CSVStrip EF BB BF; otherwise the first column is ignored
CRLF line endingsWindows exportConvert to LF, or keep CRLF but never mix
Merged cells in sheetrowspan/colspanExpand every merged cell to a full row before export
Currency mismatchStore currency vs fileShopify does not convert; prices must be in store currency
SEO fields truncatedOver 255 charsTrim meta title to 60 and description to 160
Duplicate barcodeSame GTIN twiceGTIN must be unique per variant
Negative inventoryOversoldSet quantity to 0 and use 'continue selling' if needed
Missing option valueBlank option cellEvery variant needs every option filled
Handle with spacesTitle used as handleHandles are lowercase, hyphens only

2. Preflight checker (Python)

Save as preflight_check.py and run python preflight_check.py products.csv before any import.

#!/usr/bin/env python3
"""Preflight check for a Shopify product CSV. Run before importing.

    python preflight_check.py products.csv

Prints every problem it can prove, with the line number.
Exit code 1 if any blocking problem is found.
"""
import csv
import sys

REQUIRED = ["Handle", "Title", "Option1 Name", "Option1 Value"]
PRICE_COLS = ("Price", "Variant Price")


def main(path):
    problems = []
    with open(path, "rb") as f:
        if f.read(3) == b"\xef\xbb\xbf":
            problems.append("line 1: file starts with a BOM; the first header will be ignored")

    with open(path, encoding="utf-8-sig", newline="") as f:
        rows = list(csv.reader(f))
    if not rows:
        print("empty file")
        return 1

    header = [h.strip() for h in rows[0]]
    for col in REQUIRED:
        if col not in header:
            problems.append("header: missing required column %r" % col)
    price_col = next((c for c in PRICE_COLS if c in header), None)
    if price_col is None:
        problems.append("header: no price column (need one of %s)" % (PRICE_COLS,))

    width = len(header)
    seen = {}
    for i, row in enumerate(rows[1:], start=2):
        if len(row) != width:
            problems.append("line %d: %d fields, header has %d" % (i, len(row), width))
            continue
        rec = dict(zip(header, row))
        h = (rec.get("Handle") or "").strip()
        if not h:
            problems.append("line %d: empty Handle" % i)
        elif h in seen:
            problems.append("line %d: repeated handle %r" % (i, h))
        seen[h] = True
        price = (rec.get(price_col) or "").strip() if price_col else ""
        if price:
            try:
                float(price.replace(",", ""))
            except ValueError:
                problems.append("line %d: Price %r is not a number" % (i, price))

    if problems:
        print("%d problem(s):" % len(problems))
        for p in problems[:200]:
            print("  " + p)
        return 1
    print("no blocking problems found")
    return 0


if __name__ == "__main__":
    sys.exit(main(sys.argv[1] if len(sys.argv) > 1 else "products.csv"))

3. Shopify column order

Every field in Shopify's own order. Delete the sample row and paste your data.

Handle,Title,Body (HTML),Vendor,Product Category,Type,Tags,Published,Option1 Name,Option1 Value,Option2 Name,Option2 Value,Option3 Name,Option3 Value,Variant SKU,Variant Grams,Variant Inventory Tracker,Variant Inventory Qty,Variant Inventory Policy,Variant Fulfillment Service,Variant Price,Variant Compare At Price,Variant Requires Shipping,Variant Taxable,Variant Barcode,Image Src,Image Position,Image Alt Text,Gift Card,SEO Title,SEO Description,Google Shopping / Google Product Category,Status

4. Why fixes must be applied in order

Shopify reports the first problem it hits and stops. Each fix can reveal the next:

  1. BOM - the first header is silently ignored, so every column after it shifts.
  2. Column count - an unquoted comma splits a row; everything after it is wrong.
  3. Handle - empty handles create products with generated names you cannot find later.
  4. Price - a currency symbol or thousands separator makes the row invalid.
  5. Option values - a blank option on a variant row gives you an orphan variant.

5. Merged cells, briefly

A supplier sheet where Colour spans three rows uses rowspan. CSV has no rowspan, so the export writes the value once and leaves the next rows with one fewer cell - not an empty cell, a missing one. Every column after it shifts left.

Fix: unmerge and fill the value down so every row carries it explicitly.

Questions: reply to your Gumroad receipt email. More tools at ii-x.com/tools.