WooCommerce Tips

Why WooCommerce Sale Prices Sometimes Don’t Go Away (And How to Fix It)

⚠️

WooCommerce Troubleshooting

The Sale Ended. The Price Didn’t.

Why WooCommerce sale prices linger after a promotion ends β€” and what’s actually causing it in your store.

You ran a 48-hour weekend sale. It ended Sunday night. Now it’s Tuesday morning and three customers have emailed asking why the product page still shows a crossed-out price and a “Sale!” badge β€” but the cart is charging them full price at checkout.

This is one of the most disorienting WooCommerce bugs to debug, because it looks like a display problem but often isn’t. The symptom is a stale price on the frontend. The causes, though, are three completely separate things that each require a different fix. Most of the forum advice you’ll find points at cache plugins, which is sometimes right and usually incomplete.

This guide explains all three causes clearly, how to tell which one you’re dealing with, and how to fix each one β€” including what to do so it’s less likely to happen the next time you run a sale.

Why a lingering sale price is a real problem

At first glance it seems like a cosmetic issue. The price badge is wrong, but checkout is correct, so no harm done. That’s not how customers experience it.

When a customer sees a sale price on a product page but then pays the higher price at checkout, the gap creates distrust. Some will abandon the cart assuming there’s a bug. Some will complete the purchase and then contact you asking for the difference. Some will just leave a bad review about “misleading prices.”

There’s also the opposite scenario β€” where the sale price persists in the cart too, not just on the product page β€” and you end up giving away discounts you never intended to extend. One store owner on the WooCommerce support forums described running a 30%-off flash sale for a planned 24 hours. A caching issue caused the discounted prices to persist at checkout for three additional days. He didn’t notice until he checked his revenue reports. The financial damage was real.

A stuck sale price isn’t just an aesthetic annoyance. It erodes trust, can cause accidental revenue loss, and makes your store look unreliable. It’s worth understanding properly.

The three distinct causes (not just “clear your cache”)

When you search for this problem, most answers say: “clear your cache.” That can solve it, but only in one of the three scenarios. Here’s the full picture.

Three different problems, three different fixes

Cause 1: A caching layer is serving a snapshot of the page from when the sale was active.
Cause 2: The WooCommerce sale price was never cleared from the product record β€” the sale end date passed but the price field wasn’t wiped.
Cause 3: A transient (a cached database value) is still holding the old price data and hasn’t expired yet.

The confusing part is that these can look identical on the frontend. A product page showing a crossed-out price and “Sale!” badge could be caused by any of the three. The way you diagnose them is by checking what the product actually shows when you bypass each layer.

Cause 1: Page and object caching

This is the most common cause, and it’s the one all the forum answers point to. But it’s worth understanding what caching actually does here, because knowing the mechanics helps you fix it more confidently.

What’s happening

Caching plugins like WP Rocket, W3 Total Cache, LiteSpeed Cache, or WP Super Cache work by saving a copy of rendered HTML pages so they don’t have to be generated fresh on every visitor request. When your sale was active, a visitor loaded the product page and the cache stored that version β€” complete with sale badge and crossed-out price.

When the sale ended, WooCommerce updated the product record in the database. But if your caching plugin has a long TTL (time-to-live) β€” say, 24 hours β€” every visitor who arrives at that URL within the next 24 hours gets the old cached HTML. The database says “no sale,” but the visitor sees the sale page.

Some caching setups also include object caching (Redis or Memcached), which stores database query results in memory. This is a separate layer from page caching. A visitor might bypass the HTML page cache β€” by clearing it manually β€” but still get stale product pricing data if the object cache hasn’t been flushed.

How to confirm it’s the cache

The fastest way: open the product page in a private/incognito browser window with a cache-bypass query string appended to the URL, like:

https://yourstore.com/product/product-name/?nocache=1

Most caching plugins respect a nocache parameter or similar bypass. Check your specific plugin’s documentation for the right approach. If the product page now shows the correct (full) price without any sale badge, the issue is definitively the page cache β€” not the product data itself.

If you have object caching (Redis, Memcached), the cache bypass URL may still show a stale price if the object cache holds the old query results. In that case you need to flush the object cache specifically.

How to fix it

  1. Flush the page cache in your caching plugin (WP Rocket: Dashboard β†’ Clear Cache; LiteSpeed: LSCache β†’ Flush All; W3 Total Cache: Performance β†’ Purge All Caches).
  2. If you’re running Redis or Memcached, flush the object cache separately. In WP Rocket this is included in the cache clear. In other setups you may need to flush it directly via CLI (wp cache flush using WP-CLI) or through your hosting control panel.
  3. Check your cache TTL settings. A very long TTL means stale content lingers longer. For stores that run time-sensitive sales, consider setting your product page cache to expire within a few hours, not 24+.

Pro tip

Some caching plugins let you exclude specific page types from caching. If you run frequent sales, consider excluding product pages from HTML cache entirely, or setting a very short TTL for them specifically. You trade a small performance hit for pricing accuracy.

Prevention for next time

The real fix is ensuring your cache gets cleared automatically when a sale ends. WP Rocket and some other plugins can hook into WooCommerce product updates to clear relevant cached pages when product data changes. Check your caching plugin for “WooCommerce integration” or “automatic cache clearing” settings β€” enabling this means the cache updates itself when the product price changes, rather than waiting for its TTL to expire.

Cause 2: The scheduled price was never actually cleared

This is the cause most troubleshooting guides miss entirely. It’s also the most frustrating one, because clearing your cache does nothing β€” the product database record itself is the problem.

What’s happening

WooCommerce’s built-in sale price scheduling works like this: you enter a sale price and optionally set a start and end date using the “Schedule” link on the product edit page. WooCommerce uses a scheduled WordPress cron job β€” woocommerce_scheduled_sales β€” to manage these transitions. When the end date arrives, the cron job is supposed to clear the sale price from the product.

The problem is that WordPress cron is not real cron. It’s a pseudo-cron system that only fires when someone visits the site. If your store has low traffic at the scheduled end time β€” say, 3 AM β€” the cron job might not run until the next visitor arrives, which could be hours later. During that gap, the sale price is technically still set in the database.

There’s also a subtler failure mode: the cron job ran, but it failed silently due to a plugin conflict, a memory limit, or a PHP timeout. The sale end date has passed, the job “ran,” but the sale price field on the product wasn’t wiped. The product still has a sale price set, so WooCommerce still displays it as a sale β€” no matter how many times you clear the cache.

How to confirm it’s the product data

Go to the product edit page in your WooCommerce admin. Under Product Data β†’ General, look at the Sale price field. If there’s still a price in that field after your sale should have ended, the cron job didn’t clean it up. The price field should be empty for a non-sale product.

You can also check the product directly in the database. The meta key you’re looking for is _sale_price. If it has a value, the sale price is still set regardless of what the end date says.

The cache cleared, but the price is still there

If you’ve already cleared your cache and the sale price is still showing, stop blaming the cache. Go look at the actual product record. The fix is different β€” and clearing the cache again will accomplish nothing.

How to fix it

The direct fix is manual: open the product, delete the sale price from the Sale price field, and save. If you have multiple products affected, you’ll need to do this for each one. WooCommerce’s built-in bulk edit can help β€” select the affected products, choose Edit from the Bulk Actions menu, and clear the sale price field for the whole batch.

For the cron issue specifically, the longer-term fix is ensuring WordPress cron actually fires on time. There are two main approaches:

  1. Disable WordPress pseudo-cron and use real server cron instead. Add define('DISABLE_WP_CRON', true); to your wp-config.php, then set up a real cron job on your server to hit your site’s wp-cron.php file every 5 minutes. Many hosting providers offer this in their control panel. This ensures scheduled tasks run exactly when expected, not “when someone visits.”
  2. Use a cron monitoring plugin like WP Crontrol (free on WordPress.org) to inspect scheduled events and see when they last ran. If woocommerce_scheduled_sales is overdue, you can trigger it manually from there and investigate why it’s not firing automatically.

Why this is more common than people realize

Low-traffic stores are more vulnerable because cron fires less often. But even high-traffic stores hit this when sales are scheduled to end at off-peak hours, or when a plugin update temporarily breaks cron processing. It’s the kind of issue that happens once, you don’t notice for a few days, and then you’re left wondering why a product is still on sale after a sale ended two weeks ago.

Cause 3: Transient persistence

This is the most technical of the three causes, and the hardest to spot. Transients are temporary values that WooCommerce (and other plugins) store in the WordPress options table using the set_transient() function. They’re designed to cache expensive database calculations so the same query doesn’t have to run on every page load.

What’s happening

WooCommerce uses transients to cache product pricing data, catalog queries, and other computed values. When a sale ends, WooCommerce is supposed to delete the relevant transients so fresh price data gets computed and stored. Usually this works fine. But a few things can cause transients to stick around past their expiry:

  • Object caching (Redis/Memcached) is managing transients. If your hosting or caching setup routes WordPress transients through Redis or Memcached instead of the database, clearing the WordPress options table does nothing. The transient lives in memory and won’t go away until it expires naturally or the object cache is flushed.
  • A third-party plugin is caching product data independently and not listening to WooCommerce’s product update hooks. Some page builders, product customizers, and custom theme functions store price data in their own transients. WooCommerce doesn’t know about these, so it doesn’t clear them.
  • The transient expiry time is very long. A transient set to expire in 24 hours will keep serving old data for up to a day after the underlying data changes.

How to confirm it’s a transient issue

This one is harder to confirm without database access. Use a plugin like Transients Manager (free on WordPress.org) to inspect what transients are stored and when they expire. Look for anything with “product,” “price,” or “catalog” in the key name. If they have long expiry times and haven’t been cleared after your sale ended, they could be serving stale pricing data.

Another diagnostic: temporarily disable your object caching plugin (if you’re using one) and clear the WordPress options table transients via your hosting phpMyAdmin (delete rows from wp_options where option_name starts with _transient_). If the prices correct themselves after that, a transient was the culprit.

How to fix it

  1. Clear all WordPress transients. You can do this via WP-CLI: wp transient delete --all. Or use the Transients Manager plugin’s “Delete all transients” button. WooCommerce will rebuild what it needs on the next page load.
  2. If you’re using Redis or Memcached, flush the object cache. This clears all transients stored in memory, not just the database ones.
  3. Check third-party plugins for their own caching. If you’re using a product page builder, a custom pricing plugin, or a complex theme with price caching, check their documentation for how to clear their cache.

Pro tip

WooCommerce has a built-in tool to clear product transients. Go to WooCommerce β†’ Status β†’ Tools and run “Clear transients.” This is specifically designed to wipe WooCommerce’s own cached pricing data and is often more targeted than a full cache clear.

How to diagnose which cause you’re dealing with

When you’re staring at a product still showing a sale badge, work through this sequence. Each step rules out one of the three causes.

Step 1: Check the actual product record

Before touching any cache, go to the product edit page. Look at the Sale price field under Product Data β†’ General. Is there still a price there? If yes, you’re dealing with Cause 2 β€” the scheduled price wasn’t cleared. Fix it at the source first. If the sale price field is empty and the product looks correct in admin, the issue is a display layer problem (Cause 1 or 3).

Step 2: Bypass the page cache

If the product record looks clean, open the product page in a private browser window and append a cache-bypass parameter to the URL. Check your caching plugin’s documentation for the correct method. If the page now shows the correct price, you’re dealing with Cause 1. Clear the full page cache and you’re done.

Step 3: Clear WooCommerce transients

If bypassing the page cache still shows the wrong price, go to WooCommerce β†’ Status β†’ Tools and run “Clear transients.” If you have Redis/Memcached, flush the object cache as well. Reload the product page. If it corrects itself, you were dealing with Cause 3.

Step 4: If nothing above works

If the product record is clean, the page cache is bypassed, and transients are cleared, but the sale price is still showing β€” there’s a third-party plugin or custom code independently displaying pricing data. Temporarily switch to a default WooCommerce theme (Storefront) and disable non-essential plugins one by one to isolate the source.

The longer-term fix: removing the root conditions

Fixing the immediate problem is straightforward once you know which cause you’re dealing with. The more useful question is: why did this happen, and how do you reduce the chance of it happening again?

The core problem with WooCommerce’s native sale scheduling

WooCommerce’s built-in sale price scheduling was designed for simple use cases β€” set a sale on a product for a specific date range. It works acceptably well for individual products managed by a store owner who’s actively watching the store. It breaks down in a few common scenarios:

  • Stores with many products all going on sale at once (each managed separately, no central control)
  • Sales scheduled to end at off-hours when WordPress cron is unlikely to fire on time
  • Stores with aggressive caching setups that don’t have WooCommerce integration enabled
  • Sites where plugin conflicts or server resource limits cause cron failures

The fundamental issue is that the native system has no confirmation mechanism. When a sale ends, there’s no audit trail, no success/failure notification, no way to know from the admin whether the sale actually ended cleanly or left orphaned sale prices behind.

What good sale scheduling looks like

A robust sale scheduling system should be able to answer: “Did every product in this sale get its price restored correctly when the campaign ended?” Native WooCommerce can’t answer that. You’d have to manually check each product.

If you run sales regularly β€” flash promotions, weekly deals, seasonal campaigns β€” it’s worth moving to a campaign-based approach where a single scheduled event manages the lifecycle of multiple products together. This keeps everything in one place and makes it easy to verify that a sale ended cleanly.

We built Smart Cycle Discounts partly because we kept running into this exact problem in our own stores. The plugin manages discounts through a campaign lifecycle β€” when a campaign moves to Expired status, price restoration across all affected products is handled as a single atomic operation, not as a series of individual product cron tasks. There’s no “did the cron fire in time?” uncertainty, because the campaign end event triggers the cleanup directly.

That said, this approach is only one of several tools available, and any solution that gives you centralized visibility into what’s on sale and what isn’t will improve on native WooCommerce’s isolated product-by-product model.

The caching configuration worth getting right

Regardless of how you manage your sales, your caching setup should be configured to respond automatically when product prices change. The two settings that matter most:

  • Enable WooCommerce integration in your caching plugin. WP Rocket, LiteSpeed Cache, and most major caching plugins have a dedicated WooCommerce mode that handles cache invalidation when product data changes. If this isn’t enabled, price changes require manual cache clears.
  • Set an appropriate TTL for product pages. If you run time-sensitive sales, don’t cache product pages for 24 hours. A 1-4 hour TTL is a reasonable compromise between performance and accuracy for stores that run regular promotions.

Real server cron vs. WordPress pseudo-cron

If you rely on WooCommerce’s native scheduling at all, replacing WordPress pseudo-cron with real server cron is one of the highest-value infrastructure changes you can make for a store that runs regular time-sensitive promotions. It’s a one-time setup, it’s free (your hosting cPanel typically has a cron section), and it means your scheduled events fire exactly when they’re supposed to instead of “whenever the next visitor arrives.”

The setup is:

  1. Add define('DISABLE_WP_CRON', true); to your wp-config.php file (above the “That’s all, stop editing!” line).
  2. In your hosting control panel, add a cron job that runs every 5 minutes: */5 * * * * wget -q -O - https://yourstore.com/wp-cron.php?doing_wp_cron >/dev/null 2>&1

This makes your store’s scheduling significantly more reliable β€” for sales, for emails, for backups, and for anything else that depends on WordPress scheduled events.

Frequently asked questions

WooCommerce sale price still showing after sale ended β€” answers

The questions below reflect exactly what store owners search for when they hit this problem. The answers are kept concise so they’re easy to act on.

Why is my WooCommerce sale price still showing after the sale ended?

There are three possible causes: your page cache is serving an old version of the product page, the sale price wasn’t cleared from the product record when the scheduled end time passed, or a transient (cached database value) is still holding the old pricing data. Clear your cache first. If that doesn’t fix it, check whether the sale price field on the product edit page is actually empty. If it isn’t, the scheduled price cleanup failed and you need to clear it manually.

I cleared my cache but the sale badge is still showing β€” what now?

Go to the product edit page and check the Sale price field under Product Data β†’ General. If there’s still a price entered, the issue is in the product record, not the cache β€” clearing the cache won’t fix it. Remove the sale price and save the product. Then go to WooCommerce β†’ Status β†’ Tools and run “Clear transients” to flush cached pricing data. If you have Redis or Memcached, flush the object cache too.

Why did WooCommerce not remove the sale price when the scheduled end date passed?

WooCommerce uses WordPress’s built-in cron system to handle scheduled price transitions. WordPress cron is a pseudo-cron that only fires when someone visits the site β€” it doesn’t run on a real timer. If your site has low traffic at the scheduled end time, the cron job might be delayed by hours. Plugin conflicts and server resource limits can also cause cron failures. The fix is to set up real server cron via your hosting control panel and add define('DISABLE_WP_CRON', true); to wp-config.php.

Customers are seeing the sale price at checkout even though the sale ended β€” is that a different problem?

Yes. If the discounted price is applying at checkout (not just displaying on the product page), the sale price is still set in the product record or in a cached pricing transient that affects cart calculations. A display-only stale cache won’t affect the checkout price β€” the cart always fetches current data. So if the wrong price is appearing in the cart, the product’s _sale_price meta is still set. Check the product edit page and clear the sale price field manually.

Does this affect all my products or just some?

It depends on the cause. Page cache issues typically affect specific product pages depending on when they were last cached. Cron failures affect whichever products had their sale end times in the job queue when the failure occurred β€” which could be one product or all of them, depending on how the cron event was structured. If you ran a campaign using a third-party plugin, all products in that campaign are likely affected together.

How do I stop this from happening on my next sale?

Three things: enable WooCommerce integration in your caching plugin so cache clears automatically when product prices change; set up real server cron instead of relying on WordPress pseudo-cron; and consider managing sales as campaigns (where all products in a sale are managed together) rather than setting sale prices on individual products one by one. Individual product schedules give you no visibility into whether all of them ended cleanly.

Is there a WooCommerce tool I can use to clear sale prices in bulk?

Yes. Go to Products β†’ All Products, filter to find products currently on sale, select them all, and use the bulk edit function (Bulk Actions β†’ Edit) to clear the sale price field. There’s also WooCommerce β†’ Status β†’ Tools β†’ “Clear transients,” which clears cached pricing data. For a programmatic approach, WP-CLI’s wp transient delete --all clears all transients sitewide.

Wrapping up

A sale price that won’t go away is annoying precisely because there’s no single obvious place to look. The same symptom β€” a “Sale!” badge on a product page after the sale ended β€” can come from three completely different places in your stack. The forum answer that says “clear your cache” is right maybe half the time. The other half, the problem is in the product record or in a transient layer that page cache clearing doesn’t touch.

The diagnosis order matters: check the product record first, then the page cache, then the transient layer. That sequence tells you exactly what you’re dealing with and means you’re not running in circles clearing the wrong thing.

The longer-term investment is getting your infrastructure right: real server cron so scheduled events fire on time, WooCommerce-aware caching so price changes propagate correctly, and ideally a sales management approach that gives you a clear record of what went on sale, when it ended, and whether the cleanup completed. That last part β€” verifying the ending, not just the starting β€” is what native WooCommerce doesn’t give you, and it’s where the mess tends to happen.

Key Takeaways

  • A lingering sale price has three distinct causes: page cache, uncleaned product sale price, and transient persistence β€” and each needs a different fix
  • Always check the product record first. If the Sale price field isn’t empty, clearing the cache won’t solve anything
  • WordPress pseudo-cron is unreliable for time-sensitive sales β€” set up real server cron to prevent the scheduled price cleanup from running late or not at all
  • Enable WooCommerce integration in your caching plugin so price changes automatically invalidate cached pages
  • WooCommerce β†’ Status β†’ Tools β†’ “Clear transients” is a targeted tool that clears WooCommerce’s cached pricing data specifically β€” useful when a full cache clear doesn’t fix the problem
  • Managing sales as campaigns (all products together, one lifecycle event) gives you more visibility into whether everything ended cleanly than setting sale prices product by product

Want sale campaigns that clean up after themselves?

Smart Cycle Discounts handles the full campaign lifecycle β€” from activation to expiry β€” so price restoration isn’t a manual task or a cron gamble. Free to start.

Webstepper

The Webstepper Team

WordPress Plugin Developers

We’re a husband-and-wife team building WordPress tools that solve problems we faced ourselves running online stores. Our plugins are built from experience β€” no guesswork, just practical solutions.