Thank you for buying. Everything in the kit is on this page - you can read it here or download the zip.
Paste the order number from your Gumroad receipt.
It looks like a1b2c3d4-....
| Error message | Likely cause | Fix |
|---|---|---|
Invalid variant | Options/title blank | Every variant needs a parent title and at least one option value |
Duplicate handle | Two rows share a handle | Either merge them as variants or give one a new one |
Price is invalid | Text in price cell | Numbers only, no currency symbol, no thousands separator |
Leading zeros lost | Excel stored SKU as number | Format the SKU column as Text before saving |
Row has wrong column count | Comma inside a description | Quote the whole field, or switch delimiter |
Image not found | Relative path | Absolute https:// URLs, one per line, no spaces |
Variant already exists | Re-importing same SKU | Delete the product first or update by handle |
Inventory not tracked | Tracking off | Set tracker and quantity on every variant |
Product type missing | Empty taxonomy | Set a product type; Shopify uses it for collections |
Weight unit mismatch | g vs kg | Match the unit to your store setting |
Tags look like one tag | Comma inside quotes | Shopify splits on commas; use separate columns |
BOM in first header | Excel UTF-8 CSV | Strip EF BB BF; otherwise the first column is ignored |
CRLF line endings | Windows export | Convert to LF, or keep CRLF but never mix |
Merged cells in sheet | rowspan/colspan | Expand every merged cell to a full row before export |
Currency mismatch | Store currency vs file | Shopify does not convert; prices must be in store currency |
SEO fields truncated | Over 255 chars | Trim meta title to 60 and description to 160 |
Duplicate barcode | Same GTIN twice | GTIN must be unique per variant |
Negative inventory | Oversold | Set quantity to 0 and use 'continue selling' if needed |
Missing option value | Blank option cell | Every variant needs every option filled |
Handle with spaces | Title used as handle | Handles are lowercase, hyphens only |
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"))
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
Shopify reports the first problem it hits and stops. Each fix can reveal the next:
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.