Skip to main content

Import products from a spreadsheet

The spreadsheet importer is the fastest way to create or update many products at once. It runs in three steps: download a pre-filled template, upload your file and preview exactly what will change, then run the import as a background job that reports created, updated, and failed counts. Nothing is written to your catalog until the final step, so you can validate as many times as you like first.

For adding a single product, use the Add Product modal instead. To pull your existing catalog out as a spreadsheet (which doubles as a bulk-edit template you can re-import), see Export products.

Before you begin

  • You need the products.import permission. Without it the Import button is hidden and every import endpoint is blocked. See Product permissions reference.
  • Brands, categories, suppliers, tax rates, and nominal codes referenced by your file must already exist in your account — the importer matches them by name and errors on anything it can't find. It never creates them for you. Set them up first under Manage brands, Manage categories, and the accounting references.
  • The importer only sets stock and cost fields on the product record; it doesn't create opening inventory. To seed starting stock, see Set up product inventory.

Open the importer

On the products list, click Import (top right; on mobile it's in the overflow menu). The import dialog opens on Step 1 with two modes selected by radio:

  • Import Products — create new products or update existing ones. This is what the rest of this guide covers.
  • Import Supplier Links — link existing products to suppliers with pricing, MOQ, and lead time, without touching product fields. See Manage a product's suppliers.

Step 1 — Download the template and upload your file

Download the template

Click Products Template to download an XLSX workbook tailored to your account. It has three sheets:

SheetWhat it holds
Import HereThe columns to fill in, with two sample rows. Green headers are required, purple headers are image URLs, gray headers are optional.
Field GuideEvery column with its description, whether it's required, valid values, and an example.
Reference DataThe valid brand and category names from your account — copy these exactly into your data rows.

The sample rows use your account's first real brand and category and your account's default weight and dimension units, so the example is already valid for your setup.

You don't have to use the template — any CSV, TSV, or Excel file with a header row works, as does pasting delimited text directly. But the template guarantees the exact header names the auto-matcher recognizes.

Provide your data

Upload in one of three ways: drag a file onto the drop zone, click Choose File, or paste CSV/TSV text into the box. Accepted file types are .csv, .tsv, .txt, .xlsx, and .xls. The delimiter (comma, tab, or semicolon) is auto-detected, and the importer guesses whether your first row is a header — confirm the First row is a header checkbox is set correctly, then review the raw preview and click Next: Map Columns.

Don't re-save the file in Excel before importing

Excel silently rewrites long numeric barcodes into scientific notation (for example 810000000000 becomes 8.10E+11). The importer detects and rejects any barcode that looks like scientific notation rather than storing corrupted data. If you hit that error, re-export the source file and upload it without opening it in Excel first.

Step 2 — Map your columns

Each column from your file is listed with a Maps To dropdown. The importer auto-matches common header names (for example UPC, EAN, or GTIN all map to Barcode), but you can override any mapping or set it to Skip this column. Required fields (SKU and Name) are flagged; if either is unmapped, a warning shows and you can't continue.

Read-only export columns — average cost, inventory totals, stock value, and timestamps — are recognized and shown labelled as (read-only) rather than as skippable columns. They're never sent to the server, so you can safely re-import a file you exported without stripping them out first.

The mapping list also grows to match your account. It adds a dynamic column for each of your attributes (labelled Attr: <name>), for each pricing tier (labelled Price: <tier>), and for each default-supplier pricing tier (labelled Default Supplier: <tier>). Attribute columns import alongside the standard fields, and the Price: <tier> columns set that tier's selling price — see Set selling prices below. Review the mapped-data preview, then click Next: Validate.

Set attribute values

To set or update a product attribute from your spreadsheet — in bulk, across every row of the file — map a column to that attribute. The importer offers one Attr: <name> target per attribute in your account (for example Attr: Material or Attr: Capacity). A non-blank value upserts that attribute on the product; a blank cell is left untouched. This is the way to bulk-import attribute values; to define the attributes themselves first, see Manage attributes.

Set selling prices

To set a product's selling price from your spreadsheet, map a column to the pricing tier you want — the importer offers one Price: <tier> target per tier in your account. For example, a Price: Retail column with a value of 24.99 sets the product's Retail price; a Price: Wholesale column sets its Wholesale price — one column per tier you want to update.

  • The price you enter shows in the mapped-data preview and is written when the import runs.
  • Only the tiers you map are changed — any tier you don't include keeps its current price.
  • If the product has no price for that tier yet, importing one creates it.
  • The separate unit_cost column sets the product's fallback cost, not a selling price — the two are independent, and you can set either or both in the same file.

Prefer not to use a spreadsheet? Set tier prices in bulk with Bulk-edit products, or edit a single product on its pricing screen.

Step 3 — Preview and validate

Validation resolves every name to an ID, checks for duplicates and conflicts, and classifies each row as New or Update — all without writing anything. It runs server-side and is time-capped at 120 seconds, so it's safe to run repeatedly. You get a summary bar (New, Updates, Errors, Warnings) and a per-row table.

Rows with errors are highlighted and can't be imported; rows with only warnings import fine. When there are errors, filter tabs let you jump between All, Valid, and With Errors. Fix the source file and re-validate, or proceed to import just the valid rows.

How the importer decides update vs create

For each row the importer looks for an existing product in this order:

  1. By ID — if you mapped an ID column and the row has a value, it matches the product with that database ID. A non-numeric ID, or an ID that doesn't exist, is a row error.
  2. By SKU — otherwise it matches an existing product whose SKU equals the row's SKU (case-insensitive). A match means Update; no match means New.

Changing a SKU is allowed on an ID-matched row, but guarded:

  • If the new SKU is free, the row imports and you get a warning noting the SKU will change from the old value to the new one.
  • If the new SKU already belongs to a different product, the row is a hard error — the importer never silently moves a SKU off another product.

Duplicate SKUs and conflicts

  • A SKU that appears more than once in your file is an error on every occurrence — deduplicate before importing.
  • SKU matching and duplicate detection are case-insensitive, so WIDGET-1 and widget-1 are treated as the same SKU.

Lookups resolve names, not IDs

Brand, category, default supplier, default tax rate, and the six nominal codes are all matched to your existing records by name, case-insensitively. An unknown value is a row error with a message naming the field — the importer won't auto-create the referenced record. Copy names exactly from the template's Reference Data sheet (for brands and categories) or from your settings pages.

For the default-supplier link fields (supplier SKU, lead time, MOQ, target stock days), mapping matters: a column that's mapped but left blank clears that field, while an unmapped column preserves the existing value. This lets a partial-update sheet touch only the fields you include.

Images

You can supply up to nine image URLs per row: image_url_1 is the primary image and image_url_2 through image_url_9 are additional gallery images. Each must be a public http(s) URL of at most 2048 characters; invalid or over-length URLs are per-image errors, and duplicate URLs within a row are skipped with a warning. When you populate any image cell, the row's images replace the product's existing images. Leaving every image column blank leaves existing images untouched. See Add & manage product images.

Import as a background job

Click Import on the preview to queue the run. Because the import processes every row in a single database transaction — which can take minutes on a large file — it runs as the Import Products tracked job rather than blocking the request. The dialog confirms the import is queued and you can close it; progress and the final created / updated / failed counts appear in the job tray.

The whole batch runs in one transaction, so if a row throws mid-import the transaction rolls back and the job reports as failed. Kit and bundle component links are wired up after every product in the batch exists, so a component referenced by SKU or ID resolves regardless of row order.

What each column controls

The columns below are read straight from the importer. All are optional except SKU and Name.

ColumnRequiredType / formatAccepted valuesNotes
idNoIntegerExisting product IDForces an update-by-ID match; unknown ID errors the row.
skuYesText (≤255)AnyMatch key; case-insensitive; must be unique within the file.
nameYesText (≤255)AnyProduct name.
typeNoTextstandard, bundle, kitDefaults to standard on create; see type preservation below.
barcodeNoText (≤255)UPC/EAN/ISBN/GTINScientific-notation values are rejected.
mpnNoText (≤255)AnyManufacturer part number.
brandNoTextExisting brand nameResolved by name; unknown value errors.
categoryNoTextExisting category nameSet as the product's primary category.
unit_costNoNumber ≥ 0Fallback cost for valuation when no PO/FIFO history exists. Not a selling price.
Price: <tier>NoNumber ≥ 0One dynamic column per pricing tier in your account. Sets that tier's selling price; creates the tier's price row if missing. Only mapped tiers change. See Set selling prices.
weightNoNumber ≥ 0
weight_unitNoTextkg, g, lb, ozDefaults to account setting on create.
length / width / heightNoNumber ≥ 0
dimension_unitNoTextcm, mm, in, ft, mDefaults to account setting on create.
case_quantityNoNumber ≥ 0Units per case.
case_weightNoNumber ≥ 0
case_weight_unitNoTextkg, g, lb, oz
case_length / case_width / case_heightNoNumber ≥ 0
case_dimension_unitNoTextcm, mm, in, ft, m
proforma_shipping_costNoNumber ≥ 0Landed-cost estimate per unit.
proforma_landed_cost_percentageNoNumber ≥ 0Percentage
proforma_marketplace_cost_percentageNoNumber ≥ 0Percentage
fba_prep_instructionsNoTextAny
is_dropshippableNoBooleanyes, no, true, false, 1, 0, y, nBlank leaves it unchanged on update.
min_stock_levelNoInteger ≥ 0Reorder-alert threshold.
daily_average_consumptionNoNumber ≥ 0Demand-planning input.
daily_average_consumption_net_returnsNoNumber ≥ 0Demand-planning input.
tagsNoTextComma-separatedSynced to the product's tags.
image_url_1image_url_9NoURL (≤2048)http(s) URLimage_url_1 is primary; populating any replaces existing images.
is_taxableNoBooleanyes/no/true/false/1/0Blank leaves it unchanged on update.
default_tax_rateNoTextExisting tax-rate nameResolved by name.
sales_nominal_codediscount_nominal_codeNoTextExisting nominal-code nameSix accounting codes; each resolved by name. See Set a product's accounting.
default_supplier_nameNoTextExisting supplier nameCreates/updates the default supplier link.
default_supplier_skuNoTextAnyMapped-blank clears; unmapped preserves.
default_supplier_leadtimeNoInteger ≥ 0Mapped-blank clears; unmapped preserves.
default_supplier_moqNoNumber ≥ 0Mapped-blank clears; unmapped preserves.
default_supplier_target_stock_daysNoInteger ≥ 0Mapped-blank clears; unmapped preserves.
component_skusNoTextSKU->QTY, SKU2->QTY2Attaches components to this kit/bundle by SKU.
component_idsNoTextID->QTY, ...Attaches components by ID.
component_of_skuNoTextSKU->QTY, ...Makes this product a component of the named parents.
component_of_idNoTextID->QTY, ...Same, by parent ID.
attribute_<id>NoTextAnyOne column per account attribute; upserts the value.

For the authoritative field list, see Importable product columns reference and Product fields & validation reference.

Type is preserved on partial updates

Product type is written only when creating a product, or when the update row explicitly provides a non-blank type column. A partial-update sheet that omits type — for example a price-and-name update — preserves the existing product's type, so importing over a bundle or kit never silently downgrades it to standard. To convert a type deliberately, include the type column with the new value; see Convert & auto-detect product types.

Units default only on create

New products inherit your account's default weight and dimension units when those columns are blank. On an update, units are only changed when the corresponding unit column is explicitly provided — an update never overwrites units you didn't include.

Which importer the Products page uses

There are two product-import implementations in the codebase, and they behave differently. The Products page Import button always uses the importer described on this page — the modal validates against the standard, bundle, and kit types, rejects scientific-notation barcodes, resolves lookups by name, and links kit/bundle components.

A separate generic Data Imports framework also has a product definition; it accepts additional types (matrix, blemished) and a different field set. It's not reached from the Products page Import button, so ignore any behavior differences there when following this guide. An older flat-CSV product importer also exists but is legacy and not used by the Products page.

Next steps

Video transcript

Import creates or updates many products at once from a spreadsheet. Download the template for the required headers, then drag in a CSV, TSV, or Excel file, or paste the rows straight into the box. SKU matches your columns to product fields automatically — SKU and Name are required, the rest optional. Check the mapping and the data preview, then move through Validate to Import. Existing SKUs are updated; new ones are created.

Last verified: