Appearance
Price resolution
PriceEngine::price_for_user() resolves the price a customer sees in a fixed order. The first step that applies wins.
- Manager switcher override — when a manager is previewing as a tier/customer.
- Per-customer price set on the product — a negotiated price beats everything.
call_for_pricerule — force the price empty ("Call for Price").- Hide-Pricing visibility role the customer matches → empty price, unless the product force-shows the price to this customer.
- Category → role mapping (optional, per user).
skip_matrixrule — leave the incoming price untouched (everyone sees MSRP).- Tier resolution among the tiers the customer holds:
alwaysoverride ›when_pricedoverride › lowest remaining tier price.
Within a single tier
price_as_tier() computes one tier's price:
- MSRP (regular price) as the starting point.
- The tier's explicit price for the product, if set.
no_tier_discountcollapses a discount tier to its plain base price.- Multiplier fallback —
multiplier × base role's pricewhen no explicit price. - Chained
fallback_to— MSRP or another tier.
Zero prices
The effective price a customer sees (effective_price(), the lowest of the resolved regular and sale prices) treats an exact 0 as "not yet priced" and blanks it to an empty price ("Call for Price"). This matches stores where 0 is a placeholder rather than a genuine free price.
A store that sells real $0 products can keep the zero by returning true from the wc_pricebook_allow_zero_price filter — globally or per product/user:
php
add_filter( 'wc_pricebook_allow_zero_price', '__return_true' );Visibility vs. price
"Hidden" comes in two flavors, and they're independent of the number above:
- Hide Product removes the product from the catalog entirely.
- Hide Pricing keeps the product visible but empties the price ("Call for Price").
Both are driven by visibility roles and can be overridden per product by force overrides.