Importing Discounts
The Discounts import creates and updates your store's discounts from a CSV/Excel file, a Google Sheet, or directly from another connected store. It covers the six discount types you can create yourself:
| Method | Type | What it does |
|---|---|---|
| Code or Automatic | Amount off products | A percentage or fixed amount off specific products/collections. |
| Code or Automatic | Amount off order | A percentage or fixed amount off the whole order. |
| Code or Automatic | Free shipping | Free shipping, optionally limited by country and a maximum rate. |
| Code or Automatic | Buy X Get Y | Buy a quantity of one thing, get another at a discount. |
Code discounts need a code customers type at checkout; Automatic discounts apply on their own. App-managed discounts (created by another Shopify app) can't be imported — only the app that owns them can create them.
For the wizard itself — uploading, mapping, dry runs, schedules — see Importing into EcomSolo.
How discounts are matched
Rows are matched so that re-running the same file updates instead of duplicating:
- Code discounts match by their Code.
- Automatic discounts (which have no code) match by Title.
If a match exists it's updated; otherwise a new discount is created. The dry run shows the create/update split before anything is written.
The operation you pick in Step 1 decides how matches are treated:
- Create — new discounts only; a row matching an existing code/title fails.
- Update — existing discounts only; a row matching nothing fails.
- Upsert (the default) — creates or updates as needed. Re-runs are safe.
- Delete — permission-gated and irreversible; see Deleting discounts.
Columns
Only Title is always required. Code is required for code discounts, and amount/percentage types need a Value. Everything else is optional and only read when it applies to the row's type.
| Column | Notes |
|---|---|
| Title | The discount's name (shown in Shopify admin). Required. Also the match key for automatic discounts. |
| Method | Code or Automatic. If blank, a row with a Code is treated as Code, otherwise Automatic. |
| Type | Amount off products, Amount off order, Free shipping, or Buy X Get Y. |
| Code | The redeem code (e.g. SUMMER15). Required for code discounts; the match key. For multiple codes on one discount, list them ;-separated (WELCOME; WELCOME2; WELCOME3) — the first is the primary/match code, the rest are bulk-added (see Multiple codes on one discount). |
| ValueType | Percentage or Fixed amount. |
| Value | The amount — 15 means 15% for a percentage, 10 means 10 in your currency for a fixed amount. |
| StartsAt / EndsAt | When the discount is active (ISO date/time, e.g. 2026-07-01T00:00:00Z). Leave StartsAt blank to start immediately (new discounts only — updates keep the original start date). Leave EndsAt blank for open-ended. |
| UsageLimit | Total number of times the discount can be used. |
| AppliesOncePerCustomer | true/false. |
| UsesPerOrderLimit | Buy X Get Y — how many times it can apply within one order. |
| MinimumRequirementType | None, Amount (minimum subtotal), or Quantity (minimum item count). |
| MinimumValue | The subtotal amount or item quantity for the minimum requirement. |
| CustomerSelectionType | Who's eligible: All, Customers, or Segments (code discounts only). Blank means all customers on new discounts; on updates a blank column leaves the existing eligibility unchanged. |
| CustomerSelectionValues | For Customers, customer emails (; separated); for Segments, segment GIDs. |
| AppliesToType | What the value applies to: All, Products, or Collections. |
| AppliesToValues | Product or collection handles (; separated), resolved on the target store. |
| BuyType / BuyValues | Buy X Get Y — the "buy" items: Products/Collections and their handles. |
| BuyQuantity / BuyAmount | Buy X Get Y — the minimum quantity or minimum spend the customer must buy. |
| GetQuantity | Buy X Get Y — how many items the customer gets at the discount. |
| Countries | Free shipping — country codes (US;CA) or * for the rest of the world. |
| MaximumShippingPrice | Free shipping — only discount shipping rates up to this amount. |
| PurchaseType | One-time, Subscription, or Both. Leave blank for the default (one-time purchases). Subscription/Both require your shop to actually sell subscriptions — Shopify rejects them otherwise. |
| CombinesWithProductDiscounts / …OrderDiscounts / …ShippingDiscounts | true/false — which other discount classes this can stack with. |
Common export headers map automatically: Discount Title → Title,
Limit Total Times → UsageLimit, Applies To: Values → AppliesToValues, and
so on (the Matrixify-style column names are recognised out of the box).
Products, collections, and customers
Applies-to products/collections and eligible customers are matched to the target store:
- AppliesToValues / BuyValues are product or collection handles, looked up in the target store. Anything not found there is skipped (the discount is still created) — import your Products/Collections first so they resolve.
- CustomerSelectionValues for
Customersare emails, matched to target customers. Customer segments can't be matched across stores by name, so aSegmentsselection only forwards explicit segment GIDs.
Importing from another store
On the wizard's Source step, pick From another store to copy discounts from a connected store — no file needed. Code and automatic discounts of all four supported types come across, matched by code/title on the target; applies-to and eligibility references are re-resolved by handle/email. App-managed discounts are skipped. Combined with a schedule this becomes a recurring one-way discount sync.
Multiple codes on one discount
A single code discount can carry many redeem codes — one campaign, dozens or
thousands of unique codes. Put them all in the Code cell, ;-separated:
Code
WELCOME; WELCOME2; WELCOME3
- The first code (
WELCOME) is the discount's primary code — it's created with the discount and is the match key for re-runs. - The rest are added to the same discount in bulk after it's created (Shopify processes these as a background job, up to 250 codes per batch, so very large code lists are added in chunks automatically).
- The discount imports as created/updated as usual; the extra codes' outcome is reported as a note on the row (e.g. "2 additional redeem code(s) added"). If some codes are rejected — most often because a code is already in use on another discount (Shopify requires codes to be globally unique) — the row still succeeds and the note says how many were rejected.
- Copying a multi-code discount from another store brings all of its codes across (up to the first 100 that EcomSolo has synced).
Duplicates and surrounding spaces in the list are ignored, so
A; A ; b ; B becomes primary A plus one extra b.
Deleting discounts
Pick the Delete operation in Step 1 (permission-gated) and upload a file with just the identity column(s) — matching works exactly like updates: a Code for code discounts, a Title for automatic ones. (For a multi-code discount, the primary code identifies it.) No other columns are needed.
The dry run shows a red "Will delete N"; rows that match no discount are skipped (no-ops), so re-running the same delete file is safe. Deleting a discount permanently removes it from Shopify — this cannot be undone.
Results and freshness
Discounts you create or update take effect in Shopify immediately, and EcomSolo's Discounts dashboard refreshes within seconds of the run.
Sample file
A ready-to-run sample covering every case lives at
features/importing/examples/discounts-sample.csv — one row per case: a code
percentage-off-products discount, a fixed amount-off-order with dates + usage
limit + customer eligibility, a code free-shipping discount limited to
countries, a code Buy 2 Get 1 free, an automatic percentage-off-order, an
automatic free-shipping to the rest of the world, an automatic Buy X Get Y on a
collection, a minimal row, an edge row with a quoted comma in the title, and
a multi-code row (three codes on one discount).
A focused multi-code sample — a few discounts each carrying several codes plus a
single-code control — lives at
features/importing/examples/discounts-multicode-sample.csv.
Notes and limits
- Multiple codes per discount are supported — list them
;-separated in the Code cell (see Multiple codes on one discount). - App discounts aren't importable — only the owning app can create them.
Troubleshooting
| Error in the report | What it means / what to do |
|---|---|
row requires a Title | Every row needs a discount name. |
code discounts require a Code | A Code-method row is missing its redeem code. |
amount/percentage discounts require a Value | An amount/percentage type is missing its Value. |
... already been taken | A code already exists but wasn't matched from the index; re-run — the importer resolves it and updates. |
Could not find all provided country codes in shipping zones defined for the shop | Free shipping — the Countries codes must be countries your shop actually ships to. Add them to your shipping zones (Settings → Shipping) or use * for rest-of-world. |
Could not find rest of the world in shipping zones defined for the shop | Free shipping with * — the target shop needs a "Rest of world" shipping zone (Settings → Shipping). Add one, or list explicit country codes instead. |
FAQ
Will re-importing create duplicates? No — code discounts match by code and automatic discounts by title, and the operation is an upsert, so re-runs update in place.
Some products in my discount weren't included. Those products/collections aren't in the target store yet. Import them first, then re-run.
Does it delete discounts? Only if you choose the Delete operation, which is permission-gated and irreversible — see Deleting discounts. The default Upsert only creates and updates.