Competitor discount sheet: which products a Shopify or WooCommerce store has on sale, and by how much
What you get: one Google Sheet (or Excel workbook) called a discount sheet with three tabs you actually read: on_sale (every discounted product in your competitors' stores, biggest discount first, with price, compare-at price, discount % and stock), stores (per store: products, products on sale, share of the catalogue on sale, median and largest discount) and history (one row per store per week, so you see the day a sale starts). You paste one product CSV into a raw tab; formulas do the rest. Setup is about 30 minutes; each later week takes about five.
The finished discount sheet
on_sale tab
| Store | Product | Price | Compare-at | Discount | In stock |
|---|---|---|---|---|---|
| competitor-a.example | Linen shirt, sand | 1,499 | 2,499 | 40% | TRUE |
| competitor-b.example | Canvas tote | 899 | 1,199 | 25% | TRUE |
| competitor-a.example | Cotton kurta, navy | 1,799 | 1,999 | 10% | FALSE |
stores tab
| Store | Products | On sale | Share on sale | Median discount | Largest discount |
|---|---|---|---|---|---|
| competitor-a.example | 240 | 36 | 15% | 20% | 40% |
| competitor-b.example | 85 | 4 | 5% | 25% | 25% |
Why bother before Black Friday
Black Friday 2026 is Friday 27 November and Cyber Monday is 30 November. Many stores mark products down days or weeks earlier, and festive sales in other markets run on their own calendars. A weekly discount sheet answers three practical questions without guessing:
- Has a competitor started its sale yet, and on which products?
- How deep is it: a handful of clearance items, or a large share of the range?
- What are they discounting that you also sell, so you can decide whether to match, bundle or hold your price?
How "on sale" works in store data
- Shopify: a product shows a sale when its compare-at price is higher than its price. In the public
/products.jsonfeed each variant carriespriceandcompare_at_price. - WooCommerce: the public Store API (
/wp-json/wc/store/v1/products) gives each product anon_saleflag and apricesobject withregular_price,sale_priceandprice, in the currency's minor unit (cents, paise), so divide by 100 for most currencies. - Discount % in this sheet is always
1 − price ÷ compare-at price. It is the store's own claimed discount; see Step 6 before you trust it.
Step 1: pick the stores and get one product CSV
Pick 3 to 10 stores your customers really compare you with. Use each store's homepage (the whole catalogue), not just its sale page, or you cannot work out the share on sale.
Free, one or two stores
Open /products.json?limit=250&page=1 on a Shopify store, or /wp-json/wc/store/v1/products?per_page=100 on a WooCommerce store, and copy title, URL, price and compare-at (or regular) price into a sheet with the column names used below. Step-by-step: export a Shopify store's products to CSV and export a WooCommerce store's products to CSV. If a store returns 401, 403 or 404 there, or its robots.txt or terms forbid scraping, leave it out.
No code, several stores at once (Apify)
- Create a free Apify account (no card needed for the free plan; see apify.com/pricing for current free monthly usage).
- Open Shopify & WooCommerce Product + Price Tracker and click Try for free.
- Paste your store list. In JSON view:
{
"startUrls": [
{ "url": "https://competitor-a.example" },
{ "url": "https://competitor-b.example" }
],
"maxProductsPerShop": 2000,
"maxListingPages": 20,
"includeDescription": false
}
Set maxProductsPerShop above each store's real catalogue size, otherwise the share on sale is worked out on part of the range. Turn descriptions off; this sheet does not need them. When the run finishes, open the dataset and choose Export → CSV. The columns used here are shop, title, url, price, compareAtPrice, onSale, currency and inStock. Also open the run's SHOPS_SUMMARY record: it says which stores were read and which were skipped (for example because robots.txt or the store's terms forbid scraping). Skipped stores are simply missing from the sheet.
To run the same store list every week, save the input as an Apify Task and, if you like, add a weekly Schedule for it; then you only download the CSV.
Step 2: import it into a tab called raw
- Google Sheets: new sheet, rename the first tab to
raw, then File → Import → Upload and choose Replace current sheet. Leave "Convert text to numbers" on here, so prices arrive as numbers. - Excel: Data → From Text/CSV and load it into a sheet named
raw. Check thatpriceandcompareAtPricecame in as numbers, not text. - Never edit
raw. Next week, import the new CSV over it and every other tab refreshes.
Step 3: add a COL helper (one-time)
CSV column order can change between exports, so pick columns by header name. Same helper as in the call sheet guide:
- Google Sheets: Data → Named functions → Add new function. Name
COL, argumenth, definition:
=IFERROR(XLOOKUP(h, raw!$1:$1, raw!$2:$5000), "")
- Excel 365: Formulas → Name Manager → New. Name
COL, refers to:
=LAMBDA(h, IFERROR(XLOOKUP(h, raw!$1:$1, raw!$2:$5000), ""))
Raise 5000 if your stores together list more products than that.
Step 4: work out each discount (the rows tab)
Add a tab called rows, type these headers in row 1 and each formula in row 2. In Excel 365 the formulas spill on their own; leave out ARRAYFORMULA.
| Col | Header | Formula in row 2 |
|---|---|---|
| A | store | =COL("shop") |
| B | product | =COL("title") |
| C | price | =COL("price") |
| D | compare-at | =COL("compareAtPrice") |
| E | discount | see below |
| F | in stock | =COL("inStock") |
| G | currency | =COL("currency") |
| H | link | =COL("url") |
E: discount (blank when there is no real markdown)
=ARRAYFORMULA(IF(A2:A="", "", IF((C2:C>0)*(D2:D>C2:C), 1-C2:C/D2:D, 0)))
Format column E as a percentage. A product only counts as discounted when it has a price and a compare-at price higher than that price; everything else gets 0. The tracker's own onSale column should agree; if a row has onSale TRUE but no compare-at price, open the product page and check.
Step 5: the on_sale and stores tabs
on_sale: every discounted product, biggest first
Copy the headers from rows into row 1 of a new tab on_sale, then in A2:
=SORT(FILTER(rows!A2:H, rows!A2:A<>"", rows!E2:E>0), 5, FALSE)
Want only discounts of 20% or more? Change rows!E2:E>0 to rows!E2:E>=0.2. Want only products still in stock? Add , rows!F2:F=TRUE inside the FILTER.
stores: one row per store
New tab stores, headers Store, Products, On sale, Share on sale, Median discount, Largest discount. Then:
| Cell | Formula (fill B2:F2 down as far as the store list goes) |
|---|---|
| A2 | =SORT(UNIQUE(FILTER(rows!A2:A, rows!A2:A<>""))) |
| B2 | =IF(A2="", "", COUNTIF(rows!A:A, A2)) |
| C2 | =IF(A2="", "", COUNTIFS(rows!A:A, A2, rows!E:E, ">0")) |
| D2 | =IF(A2="", "", C2/B2) |
| E2 | =IF(OR(A2="", C2=0), "", MEDIAN(FILTER(rows!E2:E, rows!A2:A=A2, rows!E2:E>0))) |
| F2 | =IF(OR(A2="", C2=0), "", MAX(FILTER(rows!E2:E, rows!A2:A=A2))) |
Format D, E and F as percentages. Mixed currencies do not matter here: every figure is a ratio inside one store.
history: a weekly snapshot (two minutes)
Add a tab history with headers Date, Store, Products, On sale, Share on sale, Median discount, Largest discount. Each week after the import, select stores!A2:F, copy, and in the first empty row of history use Paste special → Values only starting in column B, then type the date in column A for those rows. Over four or five weeks this shows exactly when each store started its sale and whether it is getting deeper. A line chart of Share on sale per store makes it obvious at a glance.
Step 6: optional, products you both sell
If you stock the same branded products, add a tab mine with your own SKU or GTIN in column A and your price in column B. The tracker exports gtin and sku when the store publishes them. In rows add column I =COL("gtin") and column J:
=ARRAYFORMULA(IF(I2:I="", "", IFERROR(VLOOKUP(I2:I, mine!A:B, 2, FALSE), "")))
Column J then shows your price next to every competitor product with the same barcode, so you can filter for "they are cheaper than us and on sale". If GTINs are blank, match by eye on title; never assume two products are identical from a similar name.
Step 7: read it carefully before you react
- A compare-at price is a claim, not history. Stores set it themselves, and it can be a list price the product rarely sold at. Judge a discount against the price in last week's or last month's snapshot.
- Variants. Some products have sizes or colours at different prices. If a row looks odd, open the link and check the variant you care about.
- Selling in the EU? When you announce a price reduction yourself, EU rules (Article 6a of Directive 98/6/EC) require showing the lowest price of the previous 30 days as the "before" price. Do not copy a competitor's "was" price without checking your own price history.
- Do not race to the bottom. A competitor's sale on items they are clearing is not a reason to cut your margin on items you sell well. Use the sheet to choose a few products to match or bundle, not to discount everything.
What not to do
- Do not scrape marketplaces (Amazon, Flipkart, eBay, Etsy and similar) or stores whose robots.txt or terms forbid it. The tracker refuses them; do the same by hand.
- Do not hammer a store with frequent runs. Once a week is enough for this sheet, with a daily check only in Black Friday week.
- Do not republish a competitor's product photos or descriptions. This sheet is for your own pricing decisions.
This guide is general information, not legal advice. You are responsible for how you use the data and for following each site's terms and your local law. The Apify Actor mentioned reads only public product pages, uses an honest bot User-Agent, follows robots.txt, checks each store's terms, never logs in and never gets around blocks.
Partner programme opening soon
Partner programme opening soon: join the free waitlist. Planned terms are 50% of each referred sale (75% once you have 5+ paying customers), single level, paid through Gumroad only from revenue received.
Related
- Build a weekly competitor price sheet from Shopify stores
- Spot out-of-stock and newly added products on competitor stores each week
- Export a Shopify or WooCommerce store's product list to CSV
- Copy-paste inputs for the Contact Details and Product scrapers
- Want to reach a store's buyer or owner? Related only: Website Contact Scraper: Emails, Phones & WhatsApp (also an Unadhi Apify product) finds the public contact inbox and phone a business lists on its own site.
Unadhi Guides are free, independent explainers. We correct errors as soon as we find them: write to mininglife7@gmail.com.