SafeSale · Blog

Sale ended but Shopify prices still discounted? The fix

Why bulk discount apps leave wrong prices behind after a sale ends, how to revert sale prices in Shopify by hand, and what a price snapshot actually is.

You searched for something like “shopify sale ended prices still discounted” or “bulk discount app changed my prices”, which means the sale is over and your catalog is not. Some variants are still at the sale price, some have a compare-at price that should not be there, and a few may be at a number you never typed. This article explains why a price-rewriting sale can leave wrong prices behind, how to revert sale prices in Shopify by hand, and what a price snapshot is: the one design choice that decides whether a sale app can undo its own work.

The problem: the sale is over, the prices are not

The pattern shows up again and again in one- and two-star reviews of bulk-discount apps. On Bold Discounts’ review page a merchant wrote on December 6, 2025 that after deleting the sales and then the app, “THE SALES ARE STUCK ON MY PRODUCTS”. On Amai Bulk Discount’s review page a November 28, 2024 review reports that “MANY products went for .95 cents” and that after turning the sale off “the prices were still ACTIVE”; a July 9, 2019 review of the same app describes it as something that “would randomly turn on sale prices on certain items after a sale was ended”. We link to the pages rather than summarise them so you can read the full context, including the developers’ replies.

Three distinct symptoms hide in those reports, and they have three distinct causes: prices that never went back (a failed or missing restore), prices that went back to the wrong number (a restore computed instead of recorded), and prices that came back to life after the sale (a second write from a stale rule). All three come from the same root: a compare-at sale is a real edit to your product data.

Why it happens: Shopify has no memory of the previous price

Shopify’s own sale pricing documentation describes how a storefront sale works: you set the Price field to the lower amount and the Compare-at price to the original, and the theme shows the compare-at value crossed out. That is all a price-rewriting sale app does, at scale, through the Admin API. Nothing in Shopify records what the price was a minute ago. Once the write lands, the discounted price is the price, and the only copy of the original is wherever the app decided to keep it.

That leaves plenty of room for things to go wrong:

  • The original was never stored, or was stored in a place that gets overwritten. If an app keeps a single “previous price” per variant and a second rule (or a retry of the first one) runs, the previous price becomes the discounted price. From then on every restore restores the wrong number.
  • The restore is calculated, not recorded. Some tools undo a 20% discount by dividing by 0.8. With prices rounded to .99 or .95 that arithmetic does not round-trip, so the “restored” price is a few cents off, and after two or three sales it drifts further. It also cannot know that a variant had no compare-at price before the sale, so the crossed-out price stays.
  • The restore job dies half-way. A restore is one API write per product. Shopify’s GraphQL Admin API rate limits meter every request against a per-store budget, so a large catalog takes minutes, not seconds. If the worker crashes, is throttled and gives up, or the merchant closes the tab that was driving it, the variants after that point are left at the sale price with nothing to tell anyone.
  • The app lost access before it could restore. Shopify revokes an app’s access token the moment it is uninstalled (see access tokens), so an app that only restores prices “on uninstall” is trying to write to a store it can no longer reach. We cover that case in detail in sale prices stuck after uninstalling a discount app.
  • A stale rule fires again. A scheduled or recurring rule that was disabled but not deleted, or a queue that replays an old job, writes sale prices onto a catalog that has already been restored. That is what “randomly turn on sale prices after a sale ended” looks like from the outside.

The manual fix: revert sale prices in Shopify without the app

If you are in this situation right now, the order of operations matters. Do not start typing prices in until you know what they should be.

  1. Find a record of the originals. The best source is a product CSV export taken before the sale. Failing that: an older export in your downloads, a supplier price list, the app’s own export if it has one, or, for high-value items, recent orders from before the sale, which record the price actually charged.
  2. Export the current state. In Shopify admin go to Products → Export and export all products as CSV. This is your “after” file and your safety net if the fix itself goes wrong.
  3. Build the corrected file. Shopify’s product CSV documentation explains which columns an update needs (at minimum the URL handle and Title, plus the option columns when you touch variant data) and how overwriting existing product details on import behaves. Set the Price column to the original price and the Compare-at column to the original compare-at, or leave it empty if there was none. Shopify’s sale-pricing page is explicit that a compare-at of $0.00 is not the same as an empty compare-at, so clear the cell rather than zeroing it.
  4. Import with overwrite, then spot-check. Open a handful of products that had variants with different original prices; that is where reverse-maths restores usually show their seams.
  5. Delete, don’t disable, the old rules. If the app is still installed, remove every sale rule so nothing can fire again; then uninstall if you are leaving.

For a few dozen variants the bulk editor (select products → Edit products → add the Price and Compare-at columns) is faster than a CSV. For a few thousand, the CSV is the only sane route.

What a price snapshot actually is, and how SafeSale uses one

A snapshot is a copy of each variant’s price and compare-at price taken before anything is written, stored separately from the live product, and treated as read-only for the life of the sale. The word gets used loosely, so here is exactly what SafeSale’s snapshot does; everything below is how the app is built, not a roadmap.

  • Snapshot first, write second. When a price-rewrite sale starts, SafeSale reads the current price and compare-at of every variant in the sale and stores one snapshot row per sale and variant before the first price is written. An empty compare-at is stored as empty, not as zero.
  • The snapshot is never updated. Snapshot rows are insert-only. If a job is retried, the existing row wins; a retry can never “snapshot” a price that is already discounted. Sale prices are always computed from the snapshot, never from whatever the live price happens to be, which is why re-running the apply step cannot compound a discount.
  • Restore writes the recorded value, not a calculation. At the end time, or when you click End sale now & restore prices, SafeSale writes the exact snapshotted price and compare-at back to each variant, explicitly clearing the compare-at where it was empty before. A variant whose live price already matches its snapshot is skipped, so a restore can be re-run as many times as needed without side effects.
  • A restore cannot silently die. Apply and restore run as jobs with exponential back-off retries. A job that exhausts its attempts is marked dead rather than dropped: the sale flips to a failed state, the sale page shows which step failed, and offers Retry now and Restore original prices. Every snapshot, write, restore and failure is a row in the sale’s audit log.
  • Manual edits during the sale are detected. SafeSale subscribes to Shopify’s product update webhook. If a variant’s price changes while its sale is live and the new value is not the one SafeSale wrote, the change is logged as an external edit. The restore still uses the snapshot, so the honest limitation is this: if you changed a price on purpose mid-sale, the end-of-sale restore puts the snapshot value back and you re-apply your change afterwards.
  • Uninstall is handled, with a caveat. On the uninstall webhook SafeSale attempts to restore everything, but as noted above Shopify may already have revoked access. So the reliable path is to end your sales before uninstalling. Snapshots are kept for 90 days after uninstall; reinstalling shows a Restore all original prices button that works without a paid plan. The emergency restore guide walks through it.

One more thing a snapshot makes possible: choosing not to rewrite prices at all. SafeSale’s default mechanism is a Shopify automatic discount, which leaves stored variant prices untouched and is switched off by Shopify at the end time. Price rewriting is the opt-in mode for merchants who need the crossed-out price on product and collection pages. Our comparison of automatic discounts versus compare-at price rewrites goes through the trade-offs.

Next step

SafeSale is not yet publicly listed on the Shopify App Store; the landing page has a five-minute demo of the preview, snapshot and restore flow so you can judge it before it is. See how SafeSale runs a sale with an undo button.

Frequently asked questions

My sale ended but the prices are still discounted. Is Shopify going to fix them?

No. If the sale was run by rewriting variant prices, Shopify treats the discounted price as the real price; there is no record of the previous value inside Shopify. Either the app that wrote the prices has to write the originals back, or you restore them yourself with the bulk editor or a CSV import.

Can I revert sale prices in Shopify by reversing the percentage?

Only approximately. A 20% discount rounded to .99 cannot be undone by adding 25%, so you will end up a few cents off on most variants. Use a real record of the original prices (an export taken before the sale, an old CSV, or the app's snapshot) instead of reverse maths.

What is a price snapshot in a sale app?

A per-variant copy of the price and compare-at price taken before the app writes anything, stored separately from the live product, and used as the only source for both the sale price and the restore. In SafeSale the snapshot is written once per sale and variant and is never updated after that.

What happens if I edit a price myself while a SafeSale sale is live?

SafeSale receives Shopify's product update webhook, compares the live price to what it wrote and logs the difference as an external change in the sale's audit log. When the sale ends it still restores the value from the snapshot, so if the manual edit was intentional you re-apply it after the restore.