This is a repost. Originally published on the Arvio blog: https://arvio.a.xyz/blog/shopify-revert-sale-prices. The canonical URL points back there.
Key takeaways
- Shopify never puts a price back. A scheduled discount runs at checkout and never touches the price field. If you ran the sale by editing the price, the way back is manual, and nothing has a date on it.
- A revert is two edits, and the order matters: save the compare-at values somewhere first, restore Price, then clear Compare at price. The compare-at column is often the only surviving record of what the old price was.
- Across 40 live storefronts read on 31 August 2026, 4,863 variants carried a compare-at price exactly equal to their selling price. One store accounts for 3,329 of those; the other 39 stores hold 1,534 between them.
-
We then read the HTML of 24 product pages. On six of the twelve pages where compare-at equalled the price, the theme's markup struck through the price being charged —
£599.00crossed out beside£599.00. None of those twelve carried theon-saleclass that nine of the twelve control pages did.
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
- Products → filter to the sale → Bulk edit. Click Columns and add Compare at price; it is not in the grid by default.
- Copy the compare-at column somewhere before you touch it. For most stores it holds the pre-sale price, and it is the only place that price still exists.
- Restore Price from those values, then clear the compare-at cells.
- Read the store back afterwards — not the app's summary. There is a paste-and-run check below that pages through the catalogue and counts what is left.
What Shopify puts back, and what it does not
There are two ways to run a sale in Shopify, and they behave completely differently when it ends.
Discounts — automatic discounts and discount codes — live at checkout. They have start and end dates, and when the end date passes they stop applying. The product's price was never touched, so there is nothing to put back.
Price edits — changing Price, usually with Compare at price set to the old number so the theme draws a strikethrough — live on the product. They have no end date, and nothing in Shopify marks a price as temporary or reminds you to change it back.
The second route is what people reach for when they want the sale visible on the product page and in collection listings, because a checkout discount is invisible until checkout. That visibility is what turns the end of the sale into two edits instead of none.
What the half-finished version looks like
We read the public catalogue of 40 live Shopify stores on 31 August 2026 — 20,675 products, 85,815 variants — and for every variant carrying a compare-at price, asked whether that compare-at is actually above the selling price.
| Variants | Share of compare-at | |
|---|---|---|
| Compare-at above price | 17,864 | 74.9% |
| Compare-at not above price | 5,982 | 25.1% |
| ↳ exactly equal to the price | 4,863 | 81.3% of those |
| ↳ below the price | 1,119 | 18.7% of those |
| Total carrying a compare-at | 23,846 | 100% |
Read the per-store numbers below, not the total. One store accounts for 4,026 of those 5,982 — 67.3% of the whole thing — and a single catalogue can move a variant-level percentage anywhere you like. Here is the same finding counted by store instead:
- 26 of the 40 stores had used the field at all. Of those 26, 18 had at least one variant where the compare-at was not above the price, and 13 had at least one where the two were exactly equal.
- Excluding the store that produces most of them, the other 39 still hold 1,534 exactly-equal variants between them. We are not turning that into a percentage: the denominator is dominated by one large, clean catalogue carrying 13,518 compare-at prices with none of them broken, which would flatter the figure as badly as the first store inflated it. (Inside the excluded store, 77% of its compare-at prices are exactly equal to the price.)
- Among the 18 affected stores, the median had 53.6% of its compare-at prices sitting at or below the selling price. That per-store median is the sturdier number, because it counts stores rather than variants — a large catalogue cannot weight it the way it weights the totals above. It still shifts by a few points if you drop any one store, as any median of eighteen does.
That last number cuts against the tidy story. If this were only people forgetting the second half of a revert, you would expect a scattering of stray variants, not a median of over half. Seven of the 18 sit above 87%, four of them at 100%. A shape like that looks more like something writing compare_at = price across a whole catalogue — a pricing app's default, a theme setting, a migration — than like a hand-edit that stopped early. We cannot tell the two apart from outside, and if you are looking at your own store, that distinction is the first thing to check.
So what does the theme do with it?
Data is not rendering. So we opened the pages. On 31 August 2026 we opened 24 product pages — 12 where compare-at equalled the price, 12 with a genuine discount as a control — and read the HTML each theme served. The 24 pages come from 17 stores, and 7 of those stores appear in both groups, which lets us compare 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 everything below is about what the theme wrote into the page, not about what a shopper's browser finally painted.
| Compare-at equals price | Compare-at above price | |
|---|---|---|
| Pages read | 12 | 12 |
Carried an on-sale class |
0 | 9 |
| Contained a strikethrough element | 6 | 9 |
| The struck-through number was the selling price | 6 of 6 | 0 of 9 |
The last row is the finding. On six of the twelve equal-compare-at pages the theme emitted a strikethrough, and in every one of those 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 drew one, that never happened — there the struck-through number was always the higher, older price, which is the entire point of the field.
The seven paired stores make this sharper. For four of the seven the theme emitted a strikethrough we could read in both states, and all four behaved identically: the theme emitted a strikethrough whether the compare-at was genuine or equal, and added the on-sale class only in the genuine case. Same store, same theme, two data states — so the difference is not a theme-to-theme difference. The other three paired stores gave us no strikethrough element in either state, which brings us to the limits.
What this probe cannot settle. It reads markup, and markup is not pixels — it can mislead in both directions: a page with no <s> element might still draw a line via CSS we did not fetch, and a page with one might have it hidden. Three control pages with genuine discounts contained no strikethrough element at all, and two of those three still carried a compare class name — so even there the theme was rendering something about the compare-at price, in a form our probe does not recognise. Only one of those three came back with no price markup whatsoever. Across all 24 pages, nine of the twelve equal pages and eleven of the twelve control pages carried some price markup, so the six is a count of pages whose markup struck through the selling price, out of nine that gave us anything to read. It is not a rate.
The on-sale column is a comparison rather than a measurement: the same probe, with the same blind spot, found that class on nine of twelve real-discount pages and on none of the twelve equal ones. We did not test for a sale badge or a "you save" line, so this is one signal about how the theme classified the product, not the whole of what it displayed.
Check your own store
Open your storefront in a browser, 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 of them:
(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}`);
})();
Searching the raw JSON by eye does not work, incidentally: every variant carries a compare_at_price key whether or not it is set, and comparing two numbers per variant is not something you can do with Ctrl-F. That is the same endpoint we read for the catalogue numbers above, and it needs no app and no login.
Method 1 — the bulk editor
The fastest route for a sale you can filter down to, and the one where the missing column bites.
- Products, then filter to the products that were on sale. If you tagged them when the sale went up, filter on that tag.
- Select them, then Bulk edit.
- Click Columns and add Compare at price. The grid gives you Price by default and not this one, which is why the second half of a revert is the half that gets skipped.
- Copy that column out before you change anything. If the sale price was calculated from the old price and you never wrote the old prices down, this column is the only surviving record of them.
- Restore Price, then clear the compare-at cells.
The grid does no arithmetic. It will not reduce a column by a percentage, and it will not restore what was there before — it gives you cells, and the numbers are yours to supply.
Method 2 — the product CSV
The export/re-import route is the hardest one to half-finish, thanks to an accident of layout: in Shopify's published product CSV, Variant Price and Variant Compare At Price are columns 20 and 21 of 50 — adjacent. (We checked that against the CSV Shopify publishes on shopify.dev, downloaded 31 August 2026; a catalogue with extra columns can shift the numbering, so count your own header row rather than trusting the position.) Unless you go out of your way to hide one, editing either puts the other in front of you.
- Products → Export, as CSV.
- In the sheet, copy
Variant Compare At PriceintoVariant Price, then empty the compare-at column. - Products → Import, and tick Overwrite products with matching handles.
- Read the store back afterwards with the snippet above.
The CSV has a row for every variant, which is what makes this bigger than it looks. In the 40 stores we read, the median store had 3.07 variants per product and the spread was enormous: 1.09 at the 10th percentile, 21.97 at the 90th. The number on your Products page is not the number of rows you are about to edit. (We have published a different median, 2.59, from an earlier and separate set of storefronts — different stores, different number, which is the argument for reading your own rather than either of ours.)
Method 3 — describe the change once, then read the preview
The third route is to say what you want in a sentence and approve a preview before anything is written. That is what Arvio does: it reads the catalogue, produces old → new pairs, and writes only after you approve them. We have stopwatched that on the price field, and the write-up with its own measurement dates is here — including the rows where the preview caught a change we had not intended, which is the reason to read one.
That run measured the Price field on one store. We have not timed clearing compare-at prices at scale, so there is no figure for that in this article.
For a revert, what matters is not speed. It is seeing the old → new pairs before anything is saved, because a revert is the one edit where you already know what the right answer should look like.
Three ways the revert goes wrong
- Compare-at left below the new price. A different mechanism from the equal case: prices went up later and the old compare-at stayed where it was. 1,119 variants in our sample are in this state. We opened none of those pages, so we cannot tell you what a theme writes for that combination.
- Reverting the product but not the variants. Price lives on the variant. A product with five sizes has five prices and five compare-ats, and a filter that shows you products will not show you the variant you missed.
- Reverting to the wrong "old price." If the sale was applied on top of an earlier markdown, the compare-at may be holding the price from two sales ago. That is the second reason to read the column before clearing it.
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 the 5,000-product ceiling on that pass, and one returned no readable catalogue. That step is a filter we applied rather than a property of the stores, though the shape of it — mostly very small catalogues — is not obviously related to how anyone prices. The 24 product pages are two groups of 12, drawn from 17 of those stores, with 7 stores in both groups. Everything came from /products.json, /products/<handle>.js and the product pages themselves.
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. All were excluded before this sample existed.
What we did not verify:
-
How long any of these have been in this state. We tried using each product's
updated_atas a proxy and it does not work: across the 5,358 products carrying a compare-at price, the median is today, because that field moves for reasons unrelated to price. So we cannot date them. - Whether any of these were mistakes. A compare-at equal to the price is also what you get if something wrote both fields to the same number on purpose, and the per-store concentration above suggests that happens.
- What any of it looks like on screen. We read markup, not rendered pages, and we did not fetch external CSS. We also did not check for sale badges or "you save" copy, and we opened no collection pages.
- The admin steps. The Columns menu, the shift-click fill and the Overwrite tick are described from the current Shopify admin, not from a probe we can hand you. Prices move hourly; this is one snapshot, taken on 31 August 2026.
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, reading your public pages and reporting what it finds: run it on your own store.
FAQ
Does Shopify automatically revert prices after a sale ends?
Only if you ran the sale as a discount. Automatic discounts and discount codes have end dates and stop applying at checkout when they pass. A price edit has no end date attached to it.
How do I remove a compare at price in Shopify?
Products → select the products → Bulk edit → Columns → add Compare at price → clear the cells. Copy the column out first: it usually holds the pre-sale price, and clearing it destroys the only record you have.
Why is my sale price not showing as crossed out?
Two different faults look the same from the admin, and the console snippet above tells them apart. If the compare-at is equal to or below the price, the field is not doing what you think — in the 24 pages we read, no equal-compare-at page carried the on-sale class, and six of the nine that gave us any price markup emitted a strikethrough around the selling price itself. That is a count of pages, not a rate. If the compare-at genuinely is higher and you still see nothing, the question is your theme rather than your data.
Can I schedule a price change in Shopify?
Not for the price field. Discounts can be scheduled with start and end dates; product prices cannot. If you ran the sale by editing prices, ending it is hand work.
How do I find every product still carrying a compare at price?
Paste the console snippet above into your storefront. It keeps requesting pages until one comes back empty, counts every variant whose compare-at is not above its price, and tables the first fifty.
Which CSV columns do I need to revert prices?
Variant Price and Variant Compare At Price, plus Handle to match rows. In Shopify's published 50-column CSV they sit at positions 20 and 21, but count your own header row — extra columns shift the numbering. Tick Overwrite products with matching handles on import or nothing changes.
Does deleting a discount code change my product prices?
No. A discount code never wrote to the product's price, so there is nothing for its deletion to put back. If the product page still shows a sale after the code is gone, the sale is coming from the price and compare-at fields.
How many rows is a bulk revert, really?
One per variant. At the median ratio in our 40 stores, 3.07 variants per product, a 500-product catalogue is closer to 1,500 rows than 500. Your own ratio is on your Products page.
Originally published at https://arvio.a.xyz/blog/shopify-revert-sale-prices. More Shopify bulk-editing writeups are on the Arvio blog.
Top comments (0)