This is a repost. Originally published on the Arvio blog: https://arvio.a.xyz/blog/shopify-compare-at-price. The canonical URL points back there.
Key takeaways
- Compare at price is a display field and nothing else. Its job is to tell the theme to draw a line through a higher number. It does not compute a discount and it has no end date.
- There is one rule: it has to be above the price. When it isn't, what the theme writes into the page becomes a separate question — and we read 24 live product pages to answer it.
- The field lives on the variant, not the product, and it is not a default column in the bulk editor.
- What other stores set (40 live storefronts, read 31 August 2026): 22 had at least one variant priced below its compare-at. Across every marked-down variant in the sample the median discount was 19.05%, with 13.8% at the 10th percentile and 40.0% at the 90th. Counting by store instead, the median of those 22 had 18.0% of its variants marked down.
- At most 6.31% of live discounts land on a round multiple of 5% — one in sixteen, and that is a ceiling: prices ending in .99 drift onto the target without anyone aiming at it. Most markdowns are a price someone chose, with the percentage falling out of the arithmetic afterwards.
- 26 of the 40 stores had used the field at all, and 18 of those 26 were carrying at least one compare-at that was not above the price. What that looks like, and how to clear it, is in the companion piece.
Published 31 August 2026 by Adot Technologies Inc, the team behind Arvio. Storefront numbers were read on 31 August 2026 from endpoints any browser can fetch; the CSV column order comes from Shopify's own published product CSV, downloaded the same day. Method, sample and exclusions at the end.
The quick version
- To set one: Products → the product → Pricing → put the higher, "before" number in Compare at price and the number you are actually charging in Price.
- To set many: select the products → Bulk edit → Columns → add Compare at price. It is not in the grid by default.
- If the strikethrough is not showing: first check whether your compare-at is actually above your price. That is the field's one rule, and it fails without an error message.
What the field is — and what it isn't
Compare at price is a number stored on each variant. When it is higher than that variant's price, a Shopify theme renders the price with the compare-at struck through beside it. When it is not higher, what the theme does is a separate question, and we opened 24 product pages to answer it.
That's the whole mechanism, which is why three things merchants expect from it never happen:
- It does not create a discount. If you set compare-at to $100 and leave price at $100, no customer pays less.
- It does not compute anything. The admin has no way to derive one number from the other — no percentage markup, no formula. Both numbers are typed.
- It does not have an end date. Discount codes and automatic discounts do. This field does not, which is why a sale run through price and compare-at has to be undone by hand.
A discount lives at checkout and changes what people pay; a compare-at lives on the product page and changes what they see before they decide.
The one rule: it has to be above the price
Every guide to this field says the same thing — the compare-at has to be higher than the price for the sale display to appear. We checked what a theme actually writes when it isn't, on 12 live product pages where the two numbers were equal, against a 12-page control group with genuine discounts. The 24 pages come from 17 stores, and 7 of those appear in both groups; for the four of those seven that returned a readable strikethrough in both states, we are comparing one theme against itself in two data states.
What we looked for in the markup: an <s> or <del> element, a class name containing compare, was-price or regular-price, a class name containing on-sale, or a literal line-through. We did not fetch each theme's external CSS, so all of this is about what the theme wrote into the page rather than what a browser finally painted.
None of the twelve equal pages carried the on-sale class that nine of the twelve control pages did. That is the one signal we can compare cleanly, and it says the theme did not classify those products as being on sale.
It did not follow that nothing rendered. Six of the twelve equal pages contained a strikethrough element anyway, and in all six the number inside it was the price being charged — £599.00 crossed out, sitting next to £599.00. Across the nine control pages that contained one, that never happened. The other six equal pages contained no strikethrough element, which is weaker evidence than it looks: three control pages with real discounts also contained none, and two of those three still carried a compare class name. Our probe reads markup, and markup is not pixels in either direction. The full two-group table, the paired-store result, and how to clear a stale compare-at are in the companion piece.
It gets into that state two ways: the compare-at ends up equal to the price (4,863 variants in our sample), or it ends up below it (1,119). We cannot tell you which field moved first — the timestamps do not support that, as the Method section explains.
What other stores are actually setting
On 31 August 2026 we read the public catalogue of 40 live Shopify storefronts: 20,675 products, 85,815 variants, 23,846 of them carrying a compare-at price.
How many are running a markdown: 22 of 40 had at least one variant priced below its compare-at. (That is the catalogue state, not a claim about what their pages display.)
How deep:
| Discount off the compare-at | |
|---|---|
| 10th percentile | 13.8% |
| Median | 19.05% |
| 90th percentile | 40.0% |
| Deepest single variant | 92.6% |
How much of the catalogue: among those 22, the median store had 18.0% of its variants marked down. Five had under 5% of the catalogue on sale; four had more than half. One store — 33 products, 106 variants — had every single variant carrying a live compare-at. A store where everything is always on sale doesn't have a sale. It has a second price list.
Round numbers are the exception
6.31% of the 17,864 live discounts land on a multiple of 5% — 20% off, 25% off, 30% off — to within a rounding hair. Read that as a ceiling rather than a count. We compare rates rounded to four decimal places, so a price ending in .99 or .95 can land on the target without anybody aiming at it: $59.99 marked down from $74.99 is 20.0027%, and this test scores it as a hit.
It is also not proof of intent. To say "these were set deliberately" you would need to know how often an exact five turns up by accident when someone picks a price rather than a percentage — and the model we built to answer that did not survive our own review. It shuffled the discount ratios rather than the underlying prices, which understates the accident rate. So rather than quote a multiple we no longer trust: at most about one discount in sixteen sits on a round five, and the true share is lower.
The looser reading is worth avoiding either way. If you round each discount to the nearest whole percent and then ask whether it is a multiple of five, you get 24.3% — and that tells you nothing, because any continuous distribution lands in a rounding window about a fifth of the time. That number measures the width of the window, not what merchants do.
At least fifteen in sixteen are prices — a round number like $79 where $99 used to be, clearing a price point, matching a competitor — with the percentage falling out of the division afterwards. If you are stuck between 20% and 25%, most of the stores we read were not answering that question at all.
Check your own store
Open your storefront, open the developer console, and paste this. It pages through the catalogue rather than reading only the first 250 products, counts every variant whose compare-at is not above its price, and tables the first fifty:
(async () => {
const out = [];
let page = 1, capped = false;
for (; ; page++) {
if (page > 200) { capped = true; break; } // safety stop: 50,000 products
const res = await fetch(`/products.json?limit=250&page=${page}`);
const { products } = await res.json();
if (!products.length) break;
for (const p of products)
for (const v of p.variants) {
const was = parseFloat(v.compare_at_price), now = parseFloat(v.price);
if (was > 0 && was <= now)
out.push({ product: p.title, variant: v.title, price: v.price, compare_at: v.compare_at_price });
}
}
console.log(`${out.length} variants carry a compare-at that is not above the price`
+ (capped ? ' — stopped at 50,000 products, so this is a floor' : ''));
console.table(out.slice(0, 50));
if (out.length > 50) console.log(`table above shows the first 50 of ${out.length}`);
})();
Reading the raw JSON by eye does not work: every variant carries a compare_at_price key whether or not it is set, and a single ?limit=250 request only covers the first 250 products. That endpoint is where the catalogue numbers above came from; the page-level numbers came from the product pages themselves.
Four routes to set it
One product. Products → the product → Pricing. Compare at price sits under Price. For a variant-level product you set it per variant in the variants table, which is where a product with five sizes turns into five edits.
Many products — the bulk editor. Select the products, Bulk edit, then Columns → Compare at price. The column is not there until you add it, and the grid does no arithmetic: you can click the first cell, shift-click the last and type once to fill a range, but it will not work out "20% off" for you.
The whole catalogue — the CSV. Products → Export, edit, Products → Import with Overwrite products with matching handles ticked. In Shopify's published product CSV, Variant Price and Variant Compare At Price sit next to each other at positions 20 and 21 of 50 — we checked that against the CSV Shopify publishes on shopify.dev, downloaded 31 August 2026, and a catalogue with extra columns will shift the numbering, so count your own header row. One row per variant, not per product: the median store in our 40 had 3.07 variants per product, with a 10th-to-90th spread of 1.09 to 21.97.
Say it once and read the preview. Arvio reads the catalogue, shows you old → new pairs, and writes only after you approve them. The same route, timed on the price field with its own measurement dates, is written up separately. We have not timed compare-at edits at scale, so there is no figure for that here.
Three ways it quietly stops working
- The compare-at is not above the price. 18 of the 26 stores that had used the field at all were carrying at least one. No theme we opened classified those as a sale, though half of the twelve equal pages still emitted a strikethrough — around the selling price.
- You set it on the product and not on every variant. Price and compare-at both live on the variant, so a product page can show a strikethrough on the default variant and a plain price on the one the customer selects.
- Your theme does not draw it where you are looking. A theme can render the strikethrough on the product page and not in a collection listing, or the other way round. We only opened product pages, so we cannot tell you which is more common.
When a compare-at is the wrong tool
- When you want the discount to apply to what people pay. Use a discount, automatic or code. Compare-at changes the display; a discount changes the total.
- When the "before" price was never really your price. Several jurisdictions regulate this directly — the EU's Omnibus Directive and the UK's CMA pricing guidance both set rules about what a struck-through reference price may be and how recently it must have applied, and other markets have their own. We have not read those rules closely enough to advise on them, and this article does not try to; if you are setting a higher "was" price to make a discount look bigger, that is the question to take to someone who has.
Method, and what we could not verify
Sample. 41 live Shopify storefronts were read on 31 August 2026; 40 returned a readable catalogue and are included. They come from a public list of 230 stores whose owners had posted their own URL on the Shopify Community's Store Feedback board; 60 of those 230 were drawn at random with a fixed seed (20260826). Of those 60, the 41 read here are the ones whose catalogues could be read end to end in an earlier pass on 26 August 2026. Of the other 19: sixteen had fewer than ten products, two hit that pass's 5,000-product ceiling (a different limit from the 50,000-product safety stop in the snippet above), and one whose catalogue we could not read that day. The 24 product pages are two groups of 12, drawn from 17 of those stores, with 7 in both groups. Everything came from /products.json, /products/<handle>.js and the product pages themselves. Prices move hourly; this is one snapshot.
Whether these stores look like yours. Stores that post asking for feedback skew newer and smaller — the median catalogue here is about 120 products. The bigger distortion is that we only see the survivors: of 474 candidate domains found on that board, 244 were already not serving — 118 did not resolve, 56 returned 404, 31 were frozen for non-payment, 18 did not return JSON at all, and the remaining 21 returned other errors.
What we did not verify:
- What any of these prices means commercially. We can see that the median marked-down variant sits 19.05% below its compare-at. We cannot see whether that is a genuine markdown, a permanent price architecture, or a number that was never charged.
-
How long any of it has been in place.
updated_atdoes not work as a proxy: across the 5,358 products carrying a compare-at price — a subset of the 20,675, since most products never use the field — the median is today, because that field moves for reasons unrelated to price. - What the pages look like on screen. We read markup, not rendered pages, and did not fetch external CSS. We did not test for sale badges or "you save" copy, and opened no collection pages. Twelve pages is what twelve storefronts did; it is not a rate for Shopify.
- That checkout ignores the field. Every account of this field says the customer pays the price field and nothing consults the compare-at. We did not run a test order to confirm it.
- The admin steps. The Columns menu, the shift-click fill, the Overwrite tick, and the absence of any warning when a compare-at is set below a price are all described from the current Shopify admin, not from a probe we can hand you.
Where to look next
For compare-at prices, the console snippet above is the fastest check we know; the audit we built does not look at them. What it does look at is everything else a shopper and a crawler hit on your storefront: run it on your own store.
FAQ
What is compare at price in Shopify?
A number stored on each variant that the theme displays struck through, beside the price, when it is higher than the price. It is a display field: it does not create a discount.
Why is my compare at price not showing?
Start by checking whether it is actually above your price — that is the field's one rule, and it fails without an error message. In the 24 pages we read, no equal-compare-at page carried the on-sale class, so the theme was not treating it as a sale. If your compare-at genuinely is higher and you still see nothing, the question is your theme.
Does compare at price affect checkout?
The customer pays the price field. We did not run a test order to verify it, but nothing in the field's documented behaviour touches checkout.
How do I bulk edit compare at price in Shopify?
Select the products → Bulk edit → Columns → add Compare at price, then edit the cells. Click the first cell and shift-click the last to fill a range. The grid does not do percentage arithmetic.
Which CSV column is compare at price?
Variant Compare At Price, directly after Variant Price. In Shopify's published 50-column CSV they are positions 20 and 21, but extra columns shift the numbering — count your own header row. Import with Overwrite products with matching handles ticked.
Can I set compare at price as a percentage above the price?
Not in the admin. Both numbers are typed, and nothing in the grid or the product form calculates one from the other. Percentage-based compare-at prices mean a spreadsheet, an app, or the API.
What is a normal discount to show?
In the 40 storefronts we read, the median live discount was 19.05%, with the 10th percentile at 13.8% and the 90th at 40.0%. At most 6.31% sat on a round multiple of 5%, and that figure is a ceiling.
Is compare at price per product or per variant?
Per variant. A product with five sizes has five compare-at prices, and they can disagree.
Originally published at https://arvio.a.xyz/blog/shopify-compare-at-price. More Shopify bulk-editing writeups are on the Arvio blog.
Top comments (0)