Convert Shopify Product CSV to Hyperzod
Drop your Shopify product export and get a Hyperzod-ready import CSV — variant rows collapsed into option groups, Variant Price recalculated as deltas over the cheapest variant, Body (HTML) cleaned, images reattached by Image Position. No manual restructuring, no invented columns — only real Shopify fields and real CSVMeta headers.
Detects Shopify format automatically · Holds file until you say convert · Validates before export
1 row →
per product after grouping by Handle
Delta
base = MIN Variant Price, variants = price − base
Quote-aware
Body (HTML) with commas & quotes parsed safely
Shopify and Hyperzod describe the same product completely differently
Shopify exports one row per variant — every size and color combination gets its own row with its own Variant Price, Variant SKU, Image Src and Option1 Value. Product fields like Title, Body (HTML), Vendor appear only on the first row of each Handle. Hyperzod expects one row per product with grouped OPTION*.VARIANTS cells and additive pricing. Converting by hand means collapsing dozens of rows, cleaning HTML, and hand-calculating every delta — and a single misplaced quote shifts columns for the whole file.
Rows must be collapsed by Handle
A product with 2 sizes × 3 colors = 6 rows plus 5 image rows. Blank Handle on continuation rows must still group correctly, and Title lives only on row 1.
Prices are not copy-paste
Hyperzod stores the cheapest variant as the base PRODUCT.PRICE.SELLING and each other price as a delta. Pasting the full Shopify price into the variant field breaks every future base-price change.
Body HTML and quoting break easily
Body (HTML) contains commas, quotes and newlines. Without quote-aware parsing the row splits mid-description and every column after it shifts — image URLs land in the wrong field.
CSV Pilot does the restructuring in code — group by handle, build option trees, recalculate deltas, strip HTML, reattach images — so the exported file already matches the importer.
How it works
Upload, confirm, review, export — the hold-and-convert flow never mutates your file until you ask.
Upload the Shopify export
Click Upload CSV on /tool and select your Shopify product CSV. Detection is case-insensitive and matches Handle/URL handle + Variant Price/Price/SKU/Option1. The file is held unchanged — nothing is converted yet.
Ask to convert
Tell the agent: “Convert this CSV for Hyperzod”. The code groups rows by Handle (blank handles = continuation), drops Default Title placeholders, and builds the option tree from Option1–3 Name/Value.
Code rebuilds the product
For each Handle: base = MIN Variant Price, variant deltas = price − base (rounded 2dp), HTML stripped from Body (HTML), Tags split on commas, Status = active only if status active/true and Published ≠ FALSE, images sorted by Image Position.
Review & export
Check the preview grid — base price, option groups, inventory, images. Fix any cell inline. Then Export CSV. The download uses real CSVMeta headers and is ready for Hyperzod Admin → Catalogue → Import.
Supported data — only real Shopify fields and real Hyperzod columns
No invented headers. Left table: every Shopify column the converters in lib/shopify-convert.ts actually read (with aliases). Right table: every Hyperzod header from lib/schema.ts → CSVMeta. Fields you don't provide stay empty — nothing is hallucinated.
Shopify source columns → mapping
| Shopify column | Hyperzod result |
|---|---|
| Handle / URL handle | Grouping key only — not copied to PRODUCT.ID |
| Title | PRODUCT.NAME (required) |
| Body (HTML) / Description / Body | PRODUCT.DESCRIPTION — HTML stripped |
| Vendor | Category fallback (3rd) if Type/Category empty |
| Product Category | PRODUCT.CATEGORY fallback (2nd) unless "uncategorized" |
| Type | PRODUCT.CATEGORY — first choice (most specific) |
| Tags | PRODUCT.TAGS — split on commas |
| Published / Published on online store | PRODUCT.STATUS — active only if Published ≠ FALSE and Status active |
| Status | active / draft / archived → PRODUCT.STATUS |
| Option1 Name / Value … Option3 | OPTION1/2/3.NAME + VARIANTS (up to 3 levels) |
| Variant Price / Price | PRODUCT.PRICE.SELLING = MIN price; others = deltas |
| Variant Compare At Price / Compare-at price | PRODUCT.PRICE.COMPARE = MAX compare (or 0) |
| Variant SKU / SKU | PRODUCT.SKU (first non-empty) + per-variant sku |
| Variant Inventory Qty / Inventory quantity | inventory per variant; blank → undefined (no stepper) |
| custom Quantity (metafields.custom) | Alternate inventory source when the above empty |
| Image Src / Product image URL | PRODUCT.IMAGES — first sorted image |
| Variant Image / Variant image URL | Variant imageUrl |
| Image Position / Image position | Sort key for images (defaults to 1) |
| Cost per item | PRODUCT.PRICE.COST + variant costPrice |
| Variant Barcode / Barcode | Dropped — warning per product (no Hyperzod field) |
| Gift Card | Row skipped entirely when true/1/yes |
Quote-aware parsing via parseCsvRows keeps Body (HTML) commas inside the field.
Hyperzod destination (CSVMeta)
| CSV header | Field |
|---|---|
| PRODUCT.NAME | name — required |
| PRODUCT.DESCRIPTION | description — HTML-cleaned Body |
| PRODUCT.SKU | sku — deduped across products |
| PRODUCT.CATEGORY | category — Type / Category / Vendor → General |
| PRODUCT.PRICE.SELLING | sellingPrice — MIN Variant Price |
| PRODUCT.PRICE.COST | costPrice — Cost per item |
| PRODUCT.PRICE.COMPARE | priceCompare — MAX compare-at (or 0) |
| PRODUCT.MIN.MAX.QUANTITY | minQty / maxQty — empty if not in Shopify |
| PRODUCT.TAX_PERCENT | taxPercent — empty if not in Shopify |
| PRODUCT.STATUS | status — active / inactive |
| PRODUCT.INVENTORY | inventory — sum of variant inventories |
| PRODUCT.LABELS | labels — empty (no Shopify source) |
| PRODUCT.TAGS | tags — from Tags |
| PRODUCT.IMAGES | imageUrl — first sorted image |
| PRODUCT.ID | id — not set from Handle (fresh import) |
| Option CSV header | Field & values |
|---|---|
| OPTION*.NAME | option name — e.g. Size |
| OPTION*.TYPE | single | multiple |
| OPTION*.ENABLE_RANGE | true / false |
| OPTION*.RANGE | [min, max] |
| OPTION*.REQUIRED | true / false |
| OPTION*.VIEW | list | card |
| OPTION*.VARIANTS | name, price delta, cost, inventory, description, image |
VARIANTS encoding
Each variant is name, price, cost, inventory, description, image. Price is the delta over base — e.g. M,10.00,0.00,50,,. Blank inventory keeps the field empty so Hyperzod does not enable the per-variant stepper.
Transformation example — real headers, real delta math
3 Shopify variant rows for one Handle → 1 Hyperzod product row with deltas that sum to the exact same prices.
| Handle | Title | Body (HTML) | Option1 Name | Option1 Value | Variant Price | Variant Compare At Price | Variant SKU | Variant Inventory Qty | Image Src | Image Position |
|---|---|---|---|---|---|---|---|---|---|---|
| classic-tee | Classic T-Shirt | <p>Soft cotton tee</p> | Size | S | 19.99 | 24.99 | TEE-S | 50 | https://cdn.example/tee.jpg | 1 |
| classic-tee | Size | M | 29.99 | 24.99 | TEE-M | 30 | https://cdn.example/tee.jpg | 1 | ||
| classic-tee | Size | L | 39.99 | 24.99 | TEE-L | https://cdn.example/tee-2.jpg | 2 |
Handle=classic-tee appears on all 3 rows. Only row 1 has Title + Body (HTML). Each row carries its own Variant Price and Option1 Value.
- • Customer pays S = $19.99, M = $29.99, L = $39.99 — flat totals per row
- • Image rows would duplicate combos with new Image Src — deduped by value tuple
- • Blank inventory on L stays blank, not 0
| PRODUCT.NAME | Classic T-Shirt |
|---|---|
| PRODUCT.DESCRIPTION | Soft cotton tee |
| PRODUCT.CATEGORY | T-Shirts (from Type) |
| PRODUCT.PRICE.SELLING | 19.99 ← MIN(19.99,29.99,39.99) |
| PRODUCT.PRICE.COMPARE | 24.99 ← MAX compare-at |
| PRODUCT.STATUS | active |
| PRODUCT.IMAGES | https://cdn.example/tee.jpg |
| OPTION1.NAME / TYPE / REQUIRED / VIEW | Size / single / true / list |
| OPTION1.VARIANTS | S,0.00,…; M,10.00,…; L,20.00,… |
Delta verification
base 19.99 + S 0.00 = 19.99 ✓ | base 19.99 + M 10.00 = 29.99 ✓ | base 19.99 + L 20.00 = 39.99 ✓
L's blank inventory exports as empty — Hyperzod leaves per-variant stepper disabled. For multi-level products (Size → Color) interior deltas are MIN of child prices and leaf deltas carry the remainder, so any path still sums to the exact Shopify price.
"S,0.00,0.00,50,Soft cotton tee,https://cdn.example/tee.jpg;M,10.00,0.00,30,,;L,20.00,0.00,,,https://cdn.example/tee-2.jpg"Example workflow — Classic T-Shirt with Size and Color
A real Shopify export shape, end-to-end. Copy this flow for your own catalog.
Your Shopify CSV
Handle=classic-tee · Title=Classic T-Shirt · Body (HTML)=<p>Soft cotton tee</p> · Vendor=Acme · Tags=vintage, summer · Published=TRUE · Status=active · Option1=Size (S/M/L) · Option2=Color (Black/White) · Variant Price per combo · Image Src×2 positions
Detection: Shopify CSV — held unchanged ✓
Tell agent: “Convert this CSV for Hyperzod”
Grouping key classic-tee collapses 6 variant rows + 2 image rows into one product.
- Vendor + Type + Tags preserved; Type wins category
- Body (HTML) → "Soft cotton tee" (tags stripped)
- Option1 Size (S/M/L) + Option2 Color (Black/White) nest — S cheapest → base
- Variant Price 19.99–39.99 → base 19.99, interior deltas = MIN child, leaf deltas remainder
- Images sorted by Position; first becomes PRODUCT.IMAGES
- Barcode rows counted → warning, not column; Gift Card rows skipped
Handle on later rows, it still groups — lastHandle carry-over handles import-template style.Preview grid
Classic T-Shirt → PRODUCT.PRICE.SELLING 19.99, Size group with Color nested
Check: cheapest combo is base? Inventory sums? Image resolves? SKU deduped?
Fix inline or chat follow-up
“Make L +$2 cheaper” → recalculates leaf delta only
Then Export CSV — the file matches the preview exactly and imports via Admin.
Open the toolShopify-aware — 8 things it gets right that generic CSV tools don't
Handle grouping + blank continuations
Groups every row by Handle/URL handle. Blank handle inherits the last handle — so import-template exports where only row 1 has the handle still collapse correctly.
Default Title dropped
Any Option level where every value is “Default Title” or empty is removed. Simple products stay flat with no fake Title option leaking into Hyperzod.
Body (HTML) cleaned
Strips tags, converts <br> to newlines, decodes & etc., and quotes the result — so a description with commas never shifts the VARIANTS image column.
Category precedence is correct
Resolves PRODUCT.CATEGORY as Type > non-uncategorized Product Category > Vendor > General — matching how merchants actually use those fields.
True Hyperzod delta pricing
Builds the nested option tree (up to 3 levels, Option1-3), then converts every price to base + delta so the sum along any Size×Color path equals the original Variant Price.
Multi-image deduping
Dedupes variant combinations by option-value tuple (same combo repeated with different Image Src) and keeps the first non-empty sku/image. Product images sorted by Image Position.
Inventory semantics preserved
Blank inventory stays undefined (empty CSV field → no per-variant stepper). Only parsed numbers are carried; parent inventory = sum of children's inventories.
Gift cards skipped, barcodes warned, SKUs deduped
Gift Card = true/1/yes rows are skipped with giftCardsSkipped count. Barcodes contribute to a per-product warning. Final pass runs dedupeSkus so exported PRODUCT.SKU values are unique.
Limitations — honest scope
The converter is deterministic and exact within its target, but it is not a full Shopify API sync and it does not bypass Hyperzod import rules.
Barcode has no field
Variant Barcode / Barcode is counted per product and reported as a warning (“dropped — Hyperzod has no barcode field”), never as a column.
Gift cards are skipped
Any row with Gift Card = true/1/yes is excluded from the export and tallied as giftCardsSkipped. Add gift-card logic in Hyperzod separately.
Max 3 option levels
Shopify supports Option1–3 and the builder mirrors it. A fourth option column has no source and is ignored. Interior price = MIN child; three levels nest correctly.
Inventory is a snapshot
PRODUCT.INVENTORY reflects the CSV at conversion time, not live Shopify stock. No metafields except custom Quantity (product.metafields.custom.custom_quantity) are read.
No image hosting
Only http URLs from Image Src / Product image URL are attached; the tool does not upload or host images. Provide public URLs you control.
Handle never becomes PRODUCT.ID
The Shopify Handle is a grouping key only. Hyperzod PRODUCT.ID stays empty so every import is fresh — handle does not leak into the product identity.
Validation is preview, not the import itself
Currency symbols on Variant Price are stripped and commas removed, but Hyperzod's importer has the final say. Fix flagged rows in the grid and export again.
Draft / Archived maps to inactive
PRODUCT.STATUS is active only when Status is active/true and Published ≠ FALSE. Everything else becomes inactive — review draft products before publishing.
Frequently asked questions
What Shopify file should I upload — export or import template?
Either. The converter detects both the Admin export (Handle, Body (HTML), Variant Price, Option1 Name/Value, Image Src) and the import-template format (URL handle, Description, Price, SKU, Option1 name/value) via case-insensitive header lookup. One row per variant is expected — blank Handle rows are treated as continuations of the last handle.
How is the Hyperzod base price and variant delta calculated?
Hyperzod uses additive pricing. The product base PRODUCT.PRICE.SELLING is the minimum Variant Price across all combinations. Each nested variant price is stored as a delta: combination price minus the base. The cheapest variant carries 0.00 delta; others carry the difference rounded to two decimals. Summing base plus deltas along any option path reconstructs the exact Shopify Variant Price.
What happens to Default Title placeholder products?
Shopify exports simple products without real options as Option1 Name = Title, Option1 Value = Default Title. The converter drops any option level where every value is empty or Default Title, so those products remain flat with no fake option group — exactly as Hyperzod expects.
Are gift cards and barcodes carried over?
Rows where Gift Card is true (true/1/yes) are skipped entirely and counted in the report as giftCardsSkipped — Hyperzod has no gift-card product type. Barcode and Variant Barcode columns are dropped with a warning per product (e.g. "Classic T-Shirt — 2 barcodes dropped — Hyperzod has no barcode field") because there is no matching Hyperzod field.
How are images and multi-image rows handled?
Image Src and Product image URL columns are collected by Image Position, deduped by URL, sorted by position, and the first image becomes PRODUCT.IMAGES. If the export repeats the same variant combination on multiple rows with different Image Src values, the converter dedupes by the option-value tuple and keeps the first non-empty image rather than creating duplicate variants. Position defaults to 1 when missing.
How is inventory mapped — does blank mean zero?
No. Variant Inventory Qty / Inventory quantity / custom Quantity (product.metafields.custom.custom_quantity) is parsed only when the cell is non-empty. Blank or missing leaves the Hyperzod inventory undefined, which exports as an empty field so the importer does NOT enable the per-variant quantity stepper. A value of 0 is preserved as 0. Parent variant inventory is the sum of its child combinations.
What product fields are actually supported?
Only fields the code reads: Handle / URL handle, Title, Body (HTML) / Description / Body (HTML stripped), Vendor, Product Category / Product category, Type, Tags (comma-split), Published (Published on online store) plus Status (active/inactive), Option1 Name/Value, Option2 Name/Value, Option3 Name/Value, Variant Price / Price, Variant Compare At Price / Compare-at price, Variant SKU / SKU, Variant Inventory Qty, Image Src / Product image URL, Image Position, Variant Image URL, Cost per item. Everything else is ignored safely with quote-aware CSV parsing for Body HTML that contains commas, quotes and newlines.
What file do I get back and how do I import it?
You get a Hyperzod-compatible CSV with PRODUCT.* headers (PRODUCT.NAME, PRODUCT.DESCRIPTION, PRODUCT.SKU, PRODUCT.PRICE.SELLING, PRODUCT.PRICE.COST, PRODUCT.PRICE.COMPARE, PRODUCT.MIN.MAX.QUANTITY, PRODUCT.TAX_PERCENT, PRODUCT.STATUS, PRODUCT.INVENTORY, PRODUCT.LABELS, PRODUCT.CATEGORY, PRODUCT.TAGS, PRODUCT.IMAGES) plus OPTION1.NAME/TYPE/ENABLE_RANGE/RANGE/REQUIRED/VIEW/VARIANTS (and OPTION2/OPTION3 as needed) with VARIANTS cells quoted and delta-priced. Import it via Hyperzod Admin → Catalogue → Import → CSV Import. SKU duplicates are deduped automatically.
Related guides
Convert Shopify CSV to Hyperzod Format
Full walkthrough of the one-row-per-variant restructuring, delta pricing and what to check after conversion.
Read guideProduct Variants in Hyperzod CSV
How Hyperzod option groups and VARIANTS deltas work — one product row with nested groups, not duplicate products.
Read guideHyperzod CSV Template Explained
Every PRODUCT.* and OPTION*.VARIANTS column decoded, with quoting and nesting rules that make imports succeed.
Read guideReady to migrate your Shopify catalog?
Upload the export, say “Convert this CSV for Hyperzod”, review the deltas and export — no spreadsheet formulas, no column guessing. Only real Shopify fields to real Hyperzod columns.
Free · No signup · Same tool as homepage · Handles, deltas and images preserved