Importing Products
This guide covers everything specific to Products imports: the columns you can use, how to lay out multi-variant products, and how to read the report when something goes wrong. For the wizard itself (sources, mapping, dry run, schedules), see Importing into EcomSolo.
Supported columns
The left column is EcomSolo's field name — what you'll see in the column mapper. The middle column lists spreadsheet headers that are recognized automatically, including the headers Shopify's own product CSV export and other common bulk import/export tools use. If your header isn't listed, no problem: auto-detect suggests the closest match, and you can always pick the field manually.
Product fields
| EcomSolo field | Recognized CSV headers | Notes |
|---|---|---|
Handle | Handle | The product's unique URL handle — used to match existing products. Required (or Title). |
Title | Title | Required when creating a new product. |
DescriptionHtml | Body (HTML), Body HTML, Description | HTML is passed through; plain text works too. |
Vendor | Vendor | |
ProductType | Type, Product Type | |
Status | Status | ACTIVE, DRAFT, or ARCHIVED (any casing — it's upper-cased for you). Leave empty to keep the existing status. |
Tags | Tags | Comma-separated list; spaces around commas are trimmed. |
Option1 Name – Option3 Name | Option1 Name etc. | The option's name (e.g. Size). Put it on the product's first row only. |
Variant fields (one per variant row)
| EcomSolo field | Recognized CSV headers | Notes |
|---|---|---|
Variants.Option1 – Variants.Option3 | Option1 Value, Option2 Value, Option3 Value | The option value for this variant (e.g. S, M, L). |
Variants.Sku | Variant SKU | |
Variants.Price | Variant Price | Decimal, e.g. 19.99. |
Variants.CompareAtPrice | Variant Compare At Price | |
Variants.Barcode | Variant Barcode | |
Variants.InventoryPolicy | — | DENY or CONTINUE (sell when out of stock). |
Variants.Taxable | — | true / false (also accepts 1, yes, y). |
Variants.InventoryItem.Sku | — | SKU on the inventory item. |
Variants.InventoryItem.Tracked | — | true / false — whether Shopify tracks inventory for this variant. |
Inventory columns (stock rides the product rows)
You can set stock levels directly from the Products sheet — no separate inventory file needed. All quantity columns apply after the product row lands, so a single file can create a product and stock it in one run.
| Column header | What it does |
|---|---|
Variant Inventory Tracker | shopify turns inventory tracking on for the variant; a blank cell turns it off. Not a boolean — don't use true/false. |
Variant Inventory Qty | Absolute available quantity. Works on single-location stores — multi-location stores get a row error pointing to the per-location columns. |
Variant Inventory Adjust | Adds/subtracts from available (single-location stores). |
Inventory Available: <Location Name> | Absolute available quantity at that location. |
Inventory Available Adjust: <Location Name> | Delta on available at that location. |
Inventory On Hand: <Location Name> | Absolute on-hand quantity at that location. |
Inventory On Hand Adjust: <Location Name> | Delta on on-hand at that location. |
Rules worth knowing:
- Blank cells are skipped — a blank never zeroes stock, it leaves that location untouched.
- Don't set and adjust the same thing on one row — an absolute column and its Adjust twin for the same quantity/location is ambiguous and fails the row (naming both columns).
- Pick Available or On Hand per variant/location, not both — Shopify links the two measures (on hand = available + committed + unavailable), so setting both in one row is order-dependent: whichever writes second recomputes the other. With nothing committed they're the same number anyway.
- Rows that stock a variant need its
Variant SKU— that's how the quantities find the right variant on multi-variant products. - Quantities need the variant to be tracked — pair them with
Variant Inventory Tracker=shopifyfor new products. - If the quantities fail (say, an unknown location name), the report shows the row as failed with the product itself already created/updated — fix the column and re-run with Upsert; only the stock is retried.
Image fields
| EcomSolo field | Recognized CSV headers | Notes |
|---|---|---|
Files.OriginalSource | Image Src, Image URL | A public URL to the image file. Shopify downloads and hosts a copy. |
Files.Alt | Image Alt Text | Alt text for the image. |
SEO fields
| EcomSolo field | Recognized CSV headers | Notes |
|---|---|---|
Seo.Title | SEO Title | The search-engine page title. |
Seo.Description | SEO Description | The search-engine meta description. |
Multi-row products: variants and extra images
A product with multiple variants spans multiple consecutive rows that repeat the same Handle — exactly like a Shopify product CSV export:
- Row 1 carries the product-level fields:
Title,Vendor,Status, tags, description — plus the option names (Option1 Name,Option2 Name,Option3 Name). - Every row (including the first) carries one variant: its
Option1 Value/Option2 Value/Option3 Value, SKU, price, and other variant fields. - Extra image rows — to attach more images than you have variants, add extra rows with just the
Handleand the image columns filled in. - Keep a product's rows together — rows for the same handle must be consecutive.
Product-level values on continuation rows can be left blank; the first row's values win. A simple product with a single variant needs only one row and no option columns at all.
Custom columns
A few headers get special treatment when they match exactly:
| Header | What it does |
|---|---|
Tags | Splits the cell on commas, trims each tag, and drops empties. |
Status | Sets product status: ACTIVE, DRAFT, or ARCHIVED. Empty keeps the current status. |
Published | Friendly boolean: true / 1 / yes / y sets the product ACTIVE; any other non-empty value sets it to DRAFT; empty leaves it unchanged. |
SEO Title | Sets the search-engine title. |
SEO Description | Sets the search-engine description. |
Metafield: namespace.key | Writes the cell value into that metafield. Example header: Metafield: custom.care_instructions. |
You can add as many Metafield: … columns as you need — one per metafield.
How matching works (upsert by handle)
With the default Upsert operation, the Handle decides what happens to each product:
- Handle exists in the store → the product is updated. Only the columns you mapped are changed; everything else on the product is left as-is.
- Handle doesn't exist → a new product is created. New products must include a
Title. - Row has neither Title nor Handle → the row is rejected and recorded in the report; the rest of the run continues.
This makes imports repeatable: run the same file twice and the second run simply updates — no duplicates. Create only, Update only and Delete match by handle the same way; see Choosing the operation for how each behaves.
Products reject Merge. Shopify's productSet treats a product's variants and options as the complete desired state — anything missing from the input is removed — so a partial-column Merge file could wipe variants. A Merge run is refused up front; use Upsert with full rows (or Update) instead.
Sample CSV (multi-variant)
Copy this into a file, save it as products.csv, and upload it as-is — every header auto-maps:
Handle,Title,Vendor,Type,Status,Option1 Name,Option1 Value,Variant SKU,Variant Price,Image Src
classic-tee,Classic Tee,Acme Apparel,Apparel,ACTIVE,Size,S,TEE-S,19.99,https://picsum.photos/seed/classictee/800/800.jpg
classic-tee,,,,,,M,TEE-M,19.99,
classic-tee,,,,,,L,TEE-L,21.99,https://picsum.photos/seed/classicteeback/800/800.jpg
solo-mug,Solo Mug,Acme Kitchen,Drinkware,ACTIVE,,,MUG-1,9.99,
What this creates:
- Classic Tee — one product with a
Sizeoption and three variants (S at 19.99, M at 19.99, L at 21.99) and two images. - Solo Mug — a simple single-variant product with no options.
Ready-to-run copies live under features/importing/examples/ — including
products-sample-multi-variant.csv, products-sample-3-rows.csv, and
products-inventory-columns-sample.csv (the inventory columns in action).
Limits and caveats
- Image URLs must be publicly reachable and point at an actual image file that Shopify can recognize (a standard image type — ideally with a file extension like
.jpg,.png, or.webpin the URL). URLs behind logins, or links to pages rather than files, are rejected by Shopify at run time. - Gift card products can't be created via import — Shopify doesn't allow creating gift card products through this API.
- Variant option combinations must be unique within a product — two rows with the same Option1/2/3 values for the same handle will be rejected by Shopify.
- File limits — CSV or
.xlsx, up to 100 MB and 50,000 rows. Excel reads the first worksheet only. - Not yet supported — uploading images as a ZIP, variant-level image assignment, and cost-per-item. (Per-location inventory quantities are supported — see Inventory columns above.)
- The dry run can't check everything (image reachability, store-wide SKU uniqueness) — those surface in the report after the real run.
Deleting products
Pick the Delete operation in Step 1 (permission-gated) and upload a file with just the Handle column — each handle is matched exactly like an update:
Handle
classic-tee
solo-mug
The dry run shows a red "Will delete N" count; a handle that matches nothing is skipped (so re-running the same delete file is safe). Deleting a product removes it and all of its variants from Shopify permanently — this cannot be undone.
Troubleshooting the import report
After every run, download the report from the import's row menu. Filter the Status column to failed and read the Errors column. Common messages:
| Error message contains | What it means | Fix |
|---|---|---|
| "Handle already in use" / "has already been taken" | Something else already owns that handle — often two different products in your file using the same handle, or a re-run where the earlier create hadn't been picked up yet. | Make sure each distinct product uses a distinct handle, and that variant rows for one product are consecutive. Then re-run — upsert will match correctly. |
| "source file not found" | The import's source file could no longer be read at run time. | Re-create the import by uploading the file again. |
| "file type is not supported for referencing" | Shopify couldn't recognize an image URL as a supported file — usually a link to a web page, a redirect, or a URL without a recognizable image type. | Use a direct, public link to the image file itself (e.g. ending in .jpg or .png) and re-run those rows. |
| "row requires at least Title or Handle" | The row had neither field, so there was nothing to match or create. | Add a Handle (updates) or a Title (creates) to the row. |
| "Title cannot be empty" (on a create) | The handle didn't match an existing product, so this row is a create — and creates need a Title. | Add a Title, or fix the handle so it matches the product you meant to update. |
Because the report repeats all your original columns, you can fix the failed rows directly in the report file, delete the extra report columns, and re-upload it — the successful rows are safe to include thanks to upsert.
FAQ
Do I have to use the standard Shopify export headers? No. Any headers work — you'll just confirm the mapping in Step 3. The common export headers are simply recognized without any manual mapping.
Can I update just one field, like Price, without touching anything else?
Yes. Map only Handle plus the columns you want to change. Unmapped fields are left untouched on existing products.
What does an empty cell do?
For Status (and Published), an empty cell means "leave unchanged," not "clear." For other fields, test the behavior on a single product with a small file first — a dry run plus one-row import is the quickest way to confirm exactly what an empty cell does to that field.
Where do imported images come from? Shopify downloads each image from the URL you provide and hosts its own copy — your source URLs aren't referenced after the import.
Can I import metafields?
Yes — add one Metafield: namespace.key column per metafield. Values are written as-is; make sure they match the metafield's type in Shopify.
Can I delete products? Yes — pick the Delete operation and upload a file of handles. See Deleting products. It's permission-gated and irreversible, and removes each product's variants too. Merge, however, is not supported for products.