A shopper scanning your shop page reads badges before they read prices. WooCommerce gives them one word: Sale! The same word sits on a product you cut by 5% and on the one you cut by half, so it tells nobody anything and it gets ignored.
A number does the opposite. -40% is specific, it is comparable against every other product in the grid, and it does the selling before anyone clicks. WooCommerce has all the data needed to print it and simply does not, so this comes down to replacing the default badge with one that shows the discount it already knows about.
This guide covers what the default badge actually shows, three ways to replace it with a real number, and how to give your deepest discounts their own treatment. By the end you will have:
- A real number on every sale product. Each badge shows that product's own discount, like -30%, worked out from its prices automatically.
- Badges that look after themselves. Percentages follow your price changes and disappear the moment a sale ends.
- Badges only where you want them. Rules by discount size, category or customer role keep the badge off products that should not carry it.

Everything in the main walkthrough below works in the free version of Advanced Woo Labels, including the targeting rules further down. No code and no upgrade needed.
What the Default WooCommerce Sale Badge Shows
Whenever a product has a sale price, WooCommerce prints a small element on the product image, on shop and category pages and on the single product page:
<span class="onsale">Sale!</span>
That is the whole feature. The markup is generated by the woocommerce_sale_flash filter, and the text is a fixed string. It carries no percentage, no amount, and no sense of scale, which is the reason two products with completely different offers look identical in a grid.

Themes restyle this badge freely, which is why it appears as a circle in one theme and a corner ribbon in another. Restyling is all most of them do, though. The word stays the word, and there is no setting anywhere in WooCommerce that turns it into a number.
Swapping that word for a number is not decoration. "-45%" cannot be misread, it lets shoppers rank your offers across a grid without opening anything, and it gives your deepest cut a reason to stand out from your smallest. Typing it by hand is not the answer though, since every price change means editing the badge and every finished sale means remembering to remove it. The number has to calculate itself from the prices already on the product.
Option 1: Add a Discount Percentage Badge Without Code
Advanced Woo Labels builds badges from two things: a display condition deciding which products get the badge, and label text that can pull live values from the product. For a discount badge that means one condition and one text variable.
To create the badge:
-
Install and activate the Advanced Woo Labels plugin from the WordPress plugin directory, or from the plugin page.
-
Hide the default badge first, otherwise you end up with two of them. Go to Adv. Woo Labels → Settings, find Show default ‘sale’ label in the General tab, and set it to Hide.

Hiding the default WooCommerce sale badge -
Go to Adv. Woo Labels → All Labels and click Add Label. Give it a title such as "Discount percentage". The title is for your own reference in the admin and never appears on the shop.
-
In the Label conditions box, add a single rule:
Product is on sale → equal to → Yes
This rule is not optional. Without it the label tries to render on every product in your catalog, and products with no sale price have a discount of zero.

The only condition a basic discount badge needs -
In the Label settings box, put this in the Label text field:
-{SAVE_PERCENT}%{SAVE_PERCENT}is a text variable. The plugin replaces it per product with that product's own discount, rounded to a whole number, so the badge renders as -30% on one product and -45% on the next. The literal%sign is yours to add, which means you can write it however you like:{SAVE_PERCENT}% OFF,Save {SAVE_PERCENT}%, or just the bare number.
Label text with the discount percentage variable -
Set the remaining label options. Template picks the shape, Position type places the badge on image or before title, and Position sets the alignment, including top, center or bottom when it sits on the image. Top left is the conventional spot and the one shoppers scan first.
Turning on Set custom styles opens colors, font size, padding, margin and opacity, with the preview updating as you go. Font size takes separate values for desktop, tablet and mobile, which is worth setting if the badge looks cramped on phones. In the sidebar, Status switches the label off without deleting it and Priority decides which label wins when several apply to the same product. Styling is covered in full below.

Shape, position and styling options with the live preview -
Click Publish, then open your shop page. Every discounted product now shows its own percentage, and every product that is not on sale shows nothing.

The finished discount percentage badge
Nothing here needs revisiting. Change a sale price and the badge follows it, end the sale and the condition stops matching, so the badge disappears on its own.
Style the Badge to Match Your Store
Turning on Set custom styles opens the full set of appearance options, and the preview beside them updates as you change each one. The free version gives you:
- Label color and Text color. Background and text. Discount badges are conventionally red or black, but the only rule that matters is contrast against your product photography, which is usually light.
- Font size, style, weight and letter spacing. Font size takes separate values for desktop, tablet and mobile, so a badge that looks right on a wide grid does not have to be shrunk everywhere else.
- Padding and margin. Padding is in em, so it scales with the font size. Change the font size and the whole badge rescales in proportion rather than needing three more adjustments. Margin is in pixels and nudges the badge away from the edge of the image.
- Opacity. Useful when the badge sits over the product itself rather than over empty background.
- Custom css. An escape hatch for anything the fields do not cover, applied to this label only.
Border, Shadow and Rotation are the three styling options reserved for PRO. They appear in the free version grayed out, so you can see where they would go.
The Template field sets the badge shape independently of all this. Five shapes ship with the free version, including a plain rectangle, rounded corners, a circle and an angled corner. PRO adds seven more, mostly ribbons and arrows that point into or away from the product image.

Show the Amount Saved Instead of a Percentage
Percentages read well on expensive items, but on a $12 product "-25%" is less persuasive than "Save $3". Keep the same condition and swap the variable in the Label text field:
Save {SYMBOL}{SAVE_AMOUNT|2}
{SYMBOL} prints your store currency symbol and {SAVE_AMOUNT} prints the money taken off. The |2 sets the number of decimal places, so the badge reads "Save $3.00" rather than "Save $3", formatted with whatever decimal and thousand separators your store is configured to use. See the sale badge with discount documentation.
Both approaches can run at once as two separate labels, or you can put them in one badge: -{SAVE_PERCENT}% ({SYMBOL}{SAVE_AMOUNT|2} off). The text variables reference lists everything else available, including {PRICE}, {SALE_PRICE}, {QTY} and {BR} for a line break.
Control Which Products Get the Badge
So far the badge appears on everything that is on sale. That is rarely what a real catalog wants. Gift cards go on sale and should not be advertised as bargains, a 3% markdown does more harm than good on a premium product, and a wholesale price cut is nobody else's business.
Every extra rule you add with the and button joins the same group, and all rules in a group have to pass. A separate group behaves as OR, so a product qualifies if any group matches. That is enough to express most real requirements. Some common ones, all using conditions available in the free version:
| What you want | Rules to add |
| Promote only the bigger discounts | Product is on sale equal to Yes Product sale discount percents greater or equal to 15 |
| Keep badges off gift cards | Product is on sale equal to Yes Product category not equal to Gift Cards |
| Never advertise a deal you cannot ship | Product is on sale equal to Yes Product stock status equal to in stock |
| A wholesale-only badge your retail customers never see | Product is on sale equal to Yes User role equal to Wholesale Customer |
| Badges only above a price point | Product is on sale equal to Yes Product price current greater or equal to 100 |
| Promote products that are nearly sold out | Product is on sale equal to Yes Product quantity less or equal to 5 |

The Product price condition takes a suboption of current, sale or regular price, which matters when you want to target the pre-discount value rather than what the shopper pays today.
A few of the more targeted rules need PRO: Day of week and Time for a weekend flash badge, User device for a mobile-only variant, Product brand and Product attributes for badges tied to a manufacturer or a color, and User purchased product or User shop stats for badges aimed at returning customers. The full list of roughly forty conditions is in the display conditions documentation.
Add the Days Left on the Sale
If your sale prices carry an end date in the product editor, {SALE_ENDS} prints the days remaining, which turns a discount badge into an urgency badge:
-{SAVE_PERCENT}%{BR}{SALE_ENDS} days left
It only produces a number where a sale end date is actually set, so it is worth checking a few products before relying on it across the catalog. For scheduled campaigns and countdown behavior there is a dedicated guide on time-sensitive labels.
Add an Animation to Draw the Eye
This one is PRO only. The label edit page gains an Animation tab where you pick one of the predefined effects, such as pulsate, shake or heartbeat, and set its Duration, Delay and Repeats number. The preview animates too, so you can judge it before anything reaches the shop.

Use it sparingly. An infinitely pulsing badge on every discounted product in a grid is noise, and shoppers filter it out the same way they filter out the word Sale. Animation earns its keep when one badge moves and the rest do not, so keep it to a single label rather than applying it everywhere. A repeat count of two or three, rather than infinite, gets the attention without the fidgeting.
If the predefined effects do not include what you want, custom animations can be registered with a snippet. See the label animations documentation and the guide on creating custom animations.
Option 2: Check Whether Your Theme Can Do It
Before adding anything, it is worth two minutes in your theme options. Several commercial WooCommerce themes ship a percentage badge toggle, usually in the customizer under a shop or product card section, sometimes labeled as a sale badge or discount label style.

If yours has one and you like how it looks, use it. The trade-offs are worth understanding first:
- One badge, one style. Theme options give you a single global badge. Tiers, different wording for different categories, or a separate badge for members are out of reach.
- It leaves with the theme. Change themes and the badge goes with it, along with any styling you tuned.
- Placement is fixed. Wherever the theme puts the badge is where it stays.
For a single straightforward percentage badge on a theme you intend to keep, that is a perfectly reasonable outcome and costs nothing.
Option 3: Add a Percentage Badge Without a Plugin (PHP Snippet)
The woocommerce_sale_flash filter hands you the badge markup before it is printed, so you can return your own. This snippet replaces the text with a percentage and handles variable products by taking the largest discount across their variations:
add_filter( 'woocommerce_sale_flash', 'kk_percentage_sale_badge', 10, 3 );
function kk_percentage_sale_badge( $html, $post, $product ) {
$percentages = array();
if ( $product->is_type( 'variable' ) ) {
foreach ( $product->get_children() as $child_id ) {
$variation = wc_get_product( $child_id );
if ( ! $variation || ! $variation->is_on_sale() ) {
continue;
}
$regular = (float) $variation->get_regular_price();
$sale = (float) $variation->get_sale_price();
if ( $regular > 0 && $sale > 0 ) {
$percentages[] = round( ( $regular - $sale ) / $regular * 100 );
}
}
} else {
$regular = (float) $product->get_regular_price();
$sale = (float) $product->get_sale_price();
if ( $regular > 0 && $sale > 0 ) {
$percentages[] = round( ( $regular - $sale ) / $regular * 100 );
}
}
if ( ! $percentages ) {
return $html;
}
return '<span class="onsale">-' . max( $percentages ) . '%</span>';
}
Put it in your child theme's functions.php or a code snippets plugin, never in the parent theme or in WooCommerce itself, where the next update will erase it.
Keeping the onsale class means your theme's existing badge styling still applies, so it should look right immediately. What you give up compared with the no-code route:
- No conditions. Every product on sale gets the badge. Tiers, category rules or customer-specific badges all mean writing more logic.
- No placement control. You get the position WooCommerce and your theme already chose.
- Styling needs code as well. The badge inherits whatever your theme already applies to
.onsale. Changing its color, shape or position means writing CSS by hand, with no preview to check it against. - Nobody else can change it. Reworded badges and seasonal campaigns become developer tickets rather than a field in the admin.
- Variations cost queries. Loading every variation of every variable product on a shop page is real work. Cache it if your catalog is large.
It is a fine answer for a small store with a developer already involved, and a poor one if the badge is going to keep changing.
FAQs
Does WooCommerce show a discount percentage by default?
No. WooCommerce prints a fixed Sale! badge on discounted products and there is no setting that turns it into a number. Showing the percentage needs a plugin, a theme that offers it, or a snippet on the woocommerce_sale_flash filter.
Do I need the PRO version for a percentage badge?
No. The Product is on sale condition, the {SAVE_PERCENT} and {SAVE_AMOUNT} variables, the Product sale discount condition used to target discount size, and the styling options are all in the free version. PRO adds things like image labels, animations, emoji, clickable labels and conditions based on user country, device, cart or purchase history.
Can I show a discount percentage without a plugin?
Yes. A snippet on the woocommerce_sale_flash filter replaces the default badge text with a calculated percentage, and there is a working one above. What it will not do is apply conditions, so every discounted product gets the same badge, and rewording it means editing code rather than a field in the admin. Some WooCommerce themes also offer a percentage badge toggle in the customizer, which is worth checking before writing anything.
How is the percentage calculated for variable products?
Every variation is compared against its own regular price, and the largest discount found is what the badge shows. It matches how WooCommerce advertises variable products generally, where the headline is the best available offer rather than an average.
Can I control rounding on the percentage?
Yes. {SAVE_PERCENT} rounds to a whole number by default. Add a pipe and a digit for decimal places, so {SAVE_PERCENT|1} renders 32.5 where the plain variable would render 33. A discount that lands on a whole number still prints without a decimal. {SAVE_AMOUNT} behaves a little differently: it pads out to exactly the number of places you ask for and follows your store currency formatting, which is why |2 is the usual choice there.
Can I show a sale badge only on specific products that are on sale?
Yes. Product is on sale is only the first rule, and anything you add beside it narrows the badge further. A category, a minimum discount, a stock level, a price floor or a user role all work, and the table above has worked examples of each. Rules in the same group all have to pass, so every rule you add means the badge appears on fewer products.
Can I show the badge only for discounts above a certain size?
Yes, with the Product sale discount condition set to percents and greater or equal to your threshold. It is also how you hide token discounts entirely if a 5% badge does more harm than good.
Will the badge disappear when the sale ends?
Yes. The condition is evaluated when the page renders, so a product that is no longer on sale stops matching and the badge is not printed. Nothing needs unpublishing or editing.
Can I use an image instead of a text badge?
Yes, by setting Label type to image, though that is a PRO feature and it accepts PNG, JPG, GIF and SVG. Worth knowing that a static image cannot show a live percentage, so image badges suit fixed campaign artwork rather than calculated discounts. See the image labels documentation.
How many badges can one product have?
There is no per-product limit on how many labels can match, but the settings page caps how many are displayed, with Max. number of labels per product defaulting to five and a separate cap per position. When several labels compete, the Priority value on each one decides the order.
Conclusion
The discount is already sitting on the product. WooCommerce knows the regular price, it knows the sale price, and then it prints a word that mentions neither. Every shopper who scans past a Sale! badge without registering what is behind it is a small, avoidable loss.
The three routes side by side:
| Method | Setup | Conditions and styling | Changed without code | Survives a theme switch |
| Advanced Woo Labels (free) | 🟢 Easy Install the plugin, one display condition, one text variable | 🟢 Full. Rules and styles, with a live preview | ✅ Yes | ✅ Yes |
| Theme option | 🟢 Easy A single toggle in the customizer, if your theme offers one | 🟡 Minimal. One global badge, one style | ✅ Yes | ❌ No, it leaves with the theme |
| PHP snippet | 🔴 Advanced Editing PHP in a child theme or a snippets plugin | 🔴 None without writing more code | ❌ No | ⚠️ Only if it lives outside the theme |
One condition and one text variable turn that word into a number that maintains itself, and a few more rules keep it off the products that should not carry it. See the sale badge with discount documentation for the option reference, or the Advanced Woo Labels feature list for what else labels can key off.