Catalog mode sounds simple: remove the price, replace “Add to cart” with “Request a quote,” and move on.
That approach works only until someone checks the data behind the page.
Modern WooCommerce stores do not render every product from one PHP template. A price may be present in the initial HTML, variation data, a Store API response, structured data, a feed, a REST response, GraphQL, or a cached fragment. Hiding the visible label can therefore create a false sense of security: the page looks price-free while the number is still available to browsers, crawlers, integrations, and curious customers.
This article explains how to treat price hiding as a data-exposure problem and how to test it systematically.
Start with a threat model
Before writing hooks, define what “hidden” means for the store.
For a wholesale catalog, it may mean:
- guests cannot retrieve a numeric price;
- approved, logged-in customers can still buy normally;
- search engines must not receive an
Offerwith a price; - read-only API credentials should not expose prices to third-party services;
- a block theme must not fetch a price after the PHP page has loaded;
- disabling the visible button must not leave the cart API writable.
This definition is much stronger than “there is no price text on the product page.” It gives you a testable security boundary.
Map every public price surface
The exact surface area depends on the theme and extensions, but a practical WooCommerce test matrix should include at least the following.
1. Classic storefront rendering
Check single-product pages, shop and category loops, related products, grouped products, variable-product ranges, the cart, the mini-cart, checkout, and customer emails.
Removing price HTML through a display filter is useful, but it covers only views that use that filter. Custom themes may call lower-level product methods directly.
2. Variation data
Variable products commonly send a JSON object containing every variation to the browser. Removing the visible range does not help if the variation payload still contains display_price, display_regular_price, or price-formatted HTML.
Inspect the document and network responses, not only the rendered text.
3. WooCommerce Store API
Block themes and modern cart/checkout blocks depend heavily on the Store API. Product responses can contain numeric price fields, formatted price HTML, ranges, and cart totals. Protection needs to happen before the response leaves the server.
A useful rule is:
If the customer is not allowed to see a price, the client should never receive that price.
Blanking a React component after fetching sensitive data is not access control.
4. Structured data and SEO integrations
Even when a theme is clean, JSON-LD may publish an Offer, price, or lowPrice. Test WooCommerce core schema plus the SEO plugins actually used by the store. A crawler does not care whether the price is visible to a human visitor.
5. REST and GraphQL
Review WooCommerce REST endpoints for products and variations. If WPGraphQL or WooGraphQL is installed, check the product fields at resolver level. A storefront rule that never reaches these APIs is incomplete.
6. Feeds and caches
Product-feed plugins, edge caches, full-page caches, and custom integrations deserve explicit tests. Some are outside the control of a catalog-mode plugin, so the admin interface should say what is protected automatically and what needs a manual check.
Remove the value server-side
CSS is presentation. JavaScript is presentation. Neither should be responsible for protecting a business-sensitive value.
The safer pattern is to enforce an audience decision on the server, then sanitize each public representation before serialization. For example, the same policy function can answer whether the current visitor may view prices, while individual adapters apply the decision to storefront output, Store API schemas, REST responses, and structured data.
Conceptually:
function visitor_can_view_catalog_prices(): bool {
if ( is_user_logged_in() && current_user_can( 'view_wholesale_prices' ) ) {
return true;
}
return false;
}
The important part is not this small function. It is using one consistent decision across every outbound surface.
Catalog mode also needs write protection
Removing “Add to cart” from the page is not enough. If the Store API still accepts a cart mutation, the missing button is merely cosmetic.
When catalog mode is active, test both halves:
- the UI no longer offers a purchase action;
- the underlying cart endpoint rejects an equivalent direct request.
The same principle applies to quote forms. Nonces help with request intent, while honeypots and rate limits reduce automated abuse. Quote records should also be separated from paid orders so they do not inflate revenue reporting.
Build a regression test, not a one-time checklist
WooCommerce, themes, and SEO plugins change. A store that passed today can leak after an update introduces a new response field.
A repeatable test should:
- request representative storefront pages as a guest;
- scan HTML and embedded JSON for known product prices;
- query Store API product and cart endpoints;
- inspect REST and GraphQL when enabled;
- parse JSON-LD for offer values;
- verify that cart writes are rejected in catalog mode;
- run again after WooCommerce, theme, or SEO-plugin updates.
False positives matter. Product IDs, SKUs, stock values, and dimensions can resemble prices, so assertions should target known fields and formats rather than blindly searching for every number.
A practical implementation
I built PriceVeil for WooCommerce around this surface-mapping approach. The free plugin covers classic storefront output, block-theme Store API responses, WooCommerce REST, common SEO schema integrations, GraphQL, carts, checkout, and quote-request handling. It also includes wp priceveil selftest so the server-reachable checks can be repeated after updates.
The broader engineering notes and screenshots are available on the PriceVeil project page. I am sharing it here because the underlying lesson applies beyond one plugin: when a business rule says a visitor must not see a value, treat every serialization boundary as part of the feature.
Final checklist
Before calling a WooCommerce catalog price-free, verify:
- the number is absent from HTML and variation JSON;
- Store API and REST responses are sanitized;
- GraphQL is covered when installed;
- JSON-LD and product feeds do not publish an offer price;
- caches have been purged and tested as a guest;
- cart writes are blocked, not merely hidden;
- logged-in exceptions behave as intended;
- there is an automated regression test for future updates.
The visible label is only the beginning. The real feature is controlling where the underlying value can travel.
Top comments (0)