Skip to content

Catalogue CSV

GET /api/admin/products.csv writes the whole catalogue, drafts included, one row per variant with the product’s own fields repeated across its rows. That is the shape Shopify exports, so the two files can be diffed. Export CSV and Import CSV on the Products screen are the same thing with a preview attached.

slug, name, kind, description, bullet_points, seo_title, seo_description, tax_code, option1_name / option1_value through option3_*, variant_sku, variant_price_cents, variant_compare_at_price_cents, variant_inventory_type, variant_inventory_quantity, variant_weight_grams, is_live, image_paths, variant_image_paths.

Lists inside one cell, bullet points and image paths, are |-separated, because the comma is taken. Prices are integer cents, and a decimal in a *_cents column is refused by name rather than rounded. An example file.

POST /api/admin/products/import/validate → { rows, creates, updates, errors[], products[] }
POST /api/admin/products/import/commit → { created, updated, skipped }

Both take the file as the request body with Content-Type: text/csv, parsed as a stream; the caps are 5 MB and 5,000 rows. Validation writes nothing and reports every error at once, each with its row and column. A merchant fixing a 500-row file one error per attempt gives up. If anything fails, the whole file is refused unless ?skipInvalid=true, which the preview offers and defaults to off; skipping is per product, since half a variant matrix is not a product. The commit re-parses and re-validates rather than trusting a token from the preview.

  • Products are matched by slug. Present is an update, absent is a create.
  • A column the file omits leaves the stored value alone. A three-column price list will not blank every description. A column that is present but empty does clear the field.
  • A variant keeps its id when its SKU matches an existing variant’s, or, for a variant with no SKU, when its option values still match, so an update does not orphan the Stripe Price behind it. An import refuses to collapse a product’s options by leaving their columns out.
  • An import never writes to Stripe. Imported products land as drafts unless is_live says otherwise, and even a live one is not published until someone publishes it.
  • Images and option groups are not managed by the file. image_paths and variant_image_paths are written on export and ignored on import.

Fields whose first character is =, +, - or @ are prefixed with an apostrophe on export, because a product named =HYPERLINK(...) is a live formula the moment the file opens in Excel. The file starts with a UTF-8 BOM so Excel reads accented names correctly.