← All samples

Shopify Product CSV Importer

PythonE-commerce

Cleans a messy supplier sheet into Shopify's product-import CSV format, grouping size variants and sending bad rows to a rejects file instead of guessing.

Run it

cd samples/shopify-importer
python to_shopify.py   # standard library only

Files: supplier_products.csv · shopify_import.csv · rejects.csv · to_shopify.py

Supplier sheet (input)

Product NameSKUSizePriceStockBrandCategoryTagsDescriptionImage
canvas tote bag tb-001$24.0040Maple & Cobagscanvas; ecoSturdy cotton canvas tote.https://example.com/img/tote.jpg
Merino Toquemt-ss32,5012accessorieswinter;woolSoft merino wool toque.https://example.com/img/toque.jpg
merino toquemt-mm32.508accessorieswinter;wool
Merino ToqueMT-LL32.50n/aaccessorieswinter;wool
Ceramic Mugcm-0114.5100Maple & Cokitchenmug12 oz stoneware mug.https://example.com/img/mug.jpg
Ceramic Mug Bluecm-0114.5030Maple & Cokitchenmug
nb-99.0020stationery
Linen Notebooknb-10call for price15stationerypaper

Shopify import file

HandleTitleBody (HTML)VendorTypeTagsPublishedOption1 NameOption1 ValueVariant SKUVariant Inventory QtyVariant PriceVariant Compare At PriceVariant Requires ShippingVariant TaxableImage SrcStatus
canvas-tote-bagCanvas Tote Bag<p>Sturdy cotton canvas tote.</p>Maple & CoBagscanvas, ecoTRUETitleDefault TitleTB-0014024.00TRUETRUEhttps://example.com/img/tote.jpgactive
merino-toqueMerino Toque<p>Soft merino wool toque.</p>House BrandAccessorieswinter, woolTRUESizeSMT-S1232.50TRUETRUEhttps://example.com/img/toque.jpgactive
merino-toqueMMT-M832.50TRUETRUE
merino-toqueLMT-L032.50TRUETRUE
ceramic-mugCeramic Mug<p>12 oz stoneware mug.</p>Maple & CoKitchenmugTRUETitleDefault TitleCM-0110014.50TRUETRUEhttps://example.com/img/mug.jpgactive

Rejected for review

rowproduct nameskusizepricestockbrandcategorytagsdescriptionimagereason
7Ceramic Mug Bluecm-0114.5030Maple & Cokitchenmugduplicate SKU
8nb-99.0020stationerymissing title
9Linen Notebooknb-10call for price15stationerypaperbad price

Code

"""Converts a messy supplier product sheet into a Shopify product-import CSV (one row per variant),
and writes rejects.csv listing rows that need a human. Run: python to_shopify.py   (standard library only)
Columns follow Shopify's documented product CSV format (Handle, Title, Option1 Name/Value, Variant SKU, Variant Price...)."""
import csv, re
COLS=["Handle","Title","Body (HTML)","Vendor","Type","Tags","Published","Option1 Name","Option1 Value","Variant SKU","Variant Inventory Qty","Variant Price","Variant Compare At Price","Variant Requires Shipping","Variant Taxable","Image Src","Status"]
def handle(t): return re.sub(r"[^a-z0-9]+","-",t.lower()).strip("-")
def price(s):
    s=(s or "").replace("$","").replace(",",".").strip() if (s or "").count(",")==1 and "." not in (s or "") else (s or "").replace("$","").replace(",","").strip()
    return f"{float(s):.2f}" if re.fullmatch(r"\d+(\.\d+)?",s) else None
out=[]; rej=[]; seen_sku=set(); first={}
for i,r in enumerate(csv.DictReader(open("supplier_products.csv",encoding="utf-8-sig")),start=2):
    r={k.strip().lower():(v or "").strip() for k,v in r.items()}
    title=" ".join(r["product name"].split()).title(); p=price(r["price"]); sku=r["sku"].upper()
    why=[w for w,bad in [("missing title",not title),("bad price",p is None),("missing SKU",not sku),("duplicate SKU",sku in seen_sku)] if bad]
    if why: rej.append({"row":i,**r,"reason":"; ".join(why)}); continue
    seen_sku.add(sku); h=handle(title); size=r["size"].upper() or "Default Title"
    qty=r["stock"] if r["stock"].isdigit() else "0"
    row=dict.fromkeys(COLS,""); row.update({"Handle":h,"Option1 Value":size,"Variant SKU":sku,"Variant Inventory Qty":qty,"Variant Price":p,"Variant Requires Shipping":"TRUE","Variant Taxable":"TRUE"})
    if h not in first:  # product-level fields only on the first row of each handle
        first[h]=True; row.update({"Title":title,"Body (HTML)":f"<p>{r['description']}</p>" if r["description"] else "","Vendor":r["brand"] or "House Brand","Type":r["category"].title(),"Tags":", ".join(t.strip().lower() for t in r["tags"].split(";") if t.strip()),"Published":"TRUE","Option1 Name":"Size" if r["size"] else "Title","Image Src":r["image"],"Status":"active"})
    out.append(row)
w=csv.DictWriter(open("shopify_import.csv","w",newline=""),fieldnames=COLS); w.writeheader(); w.writerows(out)
if rej:
    w=csv.DictWriter(open("rejects.csv","w",newline=""),fieldnames=list(rej[0])); w.writeheader(); w.writerows(rej)
print(f"{len(out)} variant rows across {len(first)} products -> shopify_import.csv; {len(rej)} rows -> rejects.csv")