Why a broken sale price costs you sales
WooCommerce treats the sale price as the price. When it is set, the storefront strikes through the regular price and charges the sale value at checkout, whether or not that value makes sense. A sale price that is negative, has a stray character, or a number equal to or above the regular price does not fail quietly; it renders.
- The discount reads backwards. A struck-through regular price next to a higher sale price looks like a pricing error, and shoppers do not buy from stores that look careless with money.
- A free promotion is a separate decision. A sale price of 0 below a positive regular price is a valid 100% discount. CatalogLift asks you to review it under the discount threshold check, without treating it as a broken sale price.
- Feeds and ads reject the product. Google Merchant Center and other feed consumers validate that the sale price is below the regular price and disapprove items that fail, so the product drops out of shopping campaigns while the mistake stands.
- Nobody sees it in wp-admin. The product list shows the effective price, not the pair; a sale price that is one cent above the regular price hides in plain sight until a customer points it out.
How to find invalid sale prices manually
Both prices live in postmeta. With WP-CLI, one query lists published simple products whose sale price is set but not a non-negative number below the regular price:
wp db query "SELECT p.ID, p.post_title,
r.meta_value AS regular_price, s.meta_value AS sale_price
FROM wp_posts p
JOIN wp_postmeta s ON s.post_id = p.ID AND s.meta_key = '_sale_price'
LEFT JOIN wp_postmeta r ON r.post_id = p.ID AND r.meta_key = '_regular_price'
WHERE p.post_type = 'product' AND p.post_status = 'publish'
AND s.meta_value <> ''
AND (
s.meta_value NOT REGEXP '^[0-9]+([.][0-9]+)?$'
OR CAST(s.meta_value AS DECIMAL(18,6)) < 0
OR CAST(s.meta_value AS DECIMAL(18,6)) >= CAST(r.meta_value AS DECIMAL(18,6))
)
ORDER BY p.ID;"
Adjust the table prefix if yours isn't wp_. The regular
expression rejects negative numbers and stray characters before the
casts run.
This works — with gaps. It does not separate simple products from
variable parents, whose price meta is derived from their variations, so
the list needs a second pass against wp_term_relationships
to keep only the simple ones. It says nothing about why each row is
there — a comma decimal, a negative amount and a price above the regular one all
look the same in the output. And it is a snapshot: the next CSV import
or price sync can write a fresh broken sale price the day after you
cleaned the list.
How CatalogLift finds and fixes it
Every scan reads the regular and sale price of each published simple product in the synced catalog. A product with no sale price is left alone. A product whose sale price is set but is not a readable decimal at least zero, or is equal to or higher than the regular price, is flagged at high severity, with the field, the regular price and the sale price attached as evidence — so you see the exact pair that collides, not just a product name.
The fix stays inside CatalogLift and involves no AI. From the finding you clear the sale price or type the corrected one, review the before/after diff, and approve. Nothing changes in the store before that decision; on it, the change goes to WooCommerce and the next scan verifies the sale price is now valid, and because CatalogLift keeps watching, an import that reintroduces the same mistake becomes a fresh finding rather than a silent regression.