Some stores should not sell everything online. A manufacturer may want buyers to ask for pricing first, while a showroom may want the catalog public and every sale handled by a person. WooCommerce can do all of this, and for simple cases you do not need a plugin at all.
This guide explains what catalog mode is, how to build it with a few lines of code that match current WooCommerce, how to show prices only to the right people, how to swap the buy button for a quote or inquiry route, and when a plugin is the safer choice. If you already know you want a plugin, our list of WooCommerce catalog plugins compares the options.
What catalog mode means
Catalog mode turns a shop into a product showcase. Visitors can browse products, read descriptions and see images, but the normal purchase path is closed. Depending on how you set it up, any of these can change:
- The Add to cart button is removed or replaced.
- Prices are hidden, either for everyone or for visitors who are not signed in.
- The cart and checkout pages are no longer reachable.
- A different call to action takes over, such as “Request a quote” or “Contact us”.
These are separate switches. Many stores only need one of them. A wholesale store usually hides prices from the public and keeps checkout working for approved buyers. A showroom usually keeps prices and removes the buy button. Decide which combination you want before touching code, because each one uses a different hook.
WooCommerce does not ship a single catalog mode toggle. We found no such setting in the WooCommerce 10.8.1 core files. What it does provide is a set of filters and actions that control purchasing and price display, and those are what the snippets below use.
Which hook controls what
This table is the map for the rest of the guide. We checked each hook name against the WooCommerce 10.8.1 source.
| Goal | Hook or approach | Where it runs |
|---|---|---|
| Stop products being bought at all | woocommerce_is_purchasable filter |
WC_Product::is_purchasable() |
| Hide the price text | woocommerce_get_price_html filter |
WC_Product::get_price_html() |
| Remove the single product button (classic templates) | remove_action on woocommerce_single_product_summary, priority 30 |
woocommerce_template_single_add_to_cart |
| Remove the shop loop button (classic templates) | remove_action on woocommerce_after_shop_loop_item, priority 10 |
woocommerce_template_loop_add_to_cart |
| Change the loop button text | woocommerce_product_add_to_cart_text filter |
Product add to cart text |
| Link out to another page or site | The External/Affiliate product type | Core product type |
The first row is the most important. Making a product non-purchasable is the real lock. Removing a button only hides the control, and the product could still be added through a direct link. Making it non-purchasable closes the door underneath.
Method 1: Use the built-in External product type
If only a few products need to send buyers elsewhere, you may not need code. WooCommerce has an External/Affiliate product type. You give it a product URL and a button label, and the button links out instead of adding to the cart. In the core code, an external product reports itself as not purchasable, so it never enters your cart.
This suits a catalog where each product page should point to a contact page, a distributor or a request form. It does not scale well to hundreds of products, because you set each product’s type and URL by hand. It also does not hide prices. For store-wide behavior, use the filters below.
Method 2: Make products non-purchasable
Add the following to a small custom plugin or your child theme’s functions.php. It marks every product as not purchasable:
add_filter( 'woocommerce_is_purchasable', '__return_false' );
That one line is the core of catalog mode. With it in place:
- The single product template for simple products stops rendering the add to cart form, because the template checks
is_purchasable()first. - The shop loop button changes to “Read more” for simple products, because the add to cart text depends on the same check.
- Cart code refuses products that are not purchasable, so a hand-built add to cart URL does not work either.
Variable products need a closer look. Purchasing is blocked for them as well, but the variation form can still render on the page, and Wbcom’s quote plugin documents the same behavior. If you sell variable products, test a variable product page and decide whether you need to hide the form with a template change.
To limit catalog mode to some products, check the product inside the callback instead of using __return_false:
add_filter( 'woocommerce_is_purchasable', function ( $purchasable, $product ) {
if ( has_term( 'quote-only', 'product_cat', $product->get_id() ) ) {
return false;
}
return $purchasable;
}, 10, 2 );
The callback takes two arguments, which is why the filter registers with 10, 2. Swap quote-only for the slug of a category you create.
Method 3: Remove the buttons (classic themes)
On a classic theme that uses the woocommerce_single_product_summary hook, you can remove the template functions directly:
add_action( 'init', function () {
remove_action( 'woocommerce_single_product_summary', 'woocommerce_template_single_add_to_cart', 30 );
remove_action( 'woocommerce_after_shop_loop_item', 'woocommerce_template_loop_add_to_cart', 10 );
} );
These priorities match WooCommerce 10.8.1. If a theme re-adds the buttons at a different priority, the removal will not match, so confirm in your theme’s templates.
Do not rely on this alone. If your theme or a page builder uses block templates, the classic actions may not run at all, and the buttons you see come from blocks. The non-purchasable filter in Method 2 works in both cases, because the block code for the add to cart button also checks is_purchasable(). Treat button removal as a cosmetic layer on top of Method 2, not as a replacement.
Hide prices
Prices are controlled separately. The woocommerce_get_price_html filter receives the price HTML and the product, so an empty string removes the price:
add_filter( 'woocommerce_get_price_html', '__return_empty_string' );
The filter runs for simple, variable and grouped products, and the WooCommerce price block calls get_price_html() too, so it covers block themes as well.
Check three places after you add it:
- The product page and shop archive. Make sure no empty gap is left where the price used to be.
- Structured data and feeds. The filter changes displayed HTML. Look in your page source and any product feeds for price data that still appears.
- Search results and widgets. Third-party widgets that print their own price markup may not use this filter.
Hiding the price and the button together is the usual “ask for a quote” setup.
Show prices only to logged-in or wholesale users
Most stores that hide prices still want some buyers to see them. Put the rule in one helper and reuse it in both filters, so the logic is in one place:
function shop_user_can_buy() {
if ( ! is_user_logged_in() ) {
return false;
}
$user = wp_get_current_user();
return in_array( 'wholesale_customer', (array) $user->roles, true );
}
add_filter( 'woocommerce_is_purchasable', function ( $purchasable ) {
return shop_user_can_buy() ? $purchasable : false;
} );
add_filter( 'woocommerce_get_price_html', function ( $html ) {
return shop_user_can_buy() ? $html : '';
} );
wholesale_customer is a placeholder. Use the role slug your store actually has. WooCommerce does not create a wholesale role by itself, so you will need to create one, or reuse the standard customer role if your goal is simply “logged-in customers only”.
If you only want to require a login, drop the role check and return is_user_logged_in().
Two practical points. First, caching: if a page cache serves the same HTML to everyone, a logged-out visitor may see prices cached from a logged-in session, or the other way around. Most caching setups skip the cache for logged-in users, but confirm yours does. Second, tell visitors what to do. A hidden price with no explanation looks like a bug. Add a line near the product such as “Sign in to see wholesale pricing” with a link to your account page.
Replace the button with a quote or inquiry route
Once the buy button is gone, give shoppers a way forward. You have three levels of effort.
A plain link
The simplest option is a button that links to your contact page, with the product in the URL so your form can pre-fill it:
add_action( 'woocommerce_single_product_summary', function () {
global $product;
$url = add_query_arg( 'product', rawurlencode( $product->get_name() ), home_url( '/contact/' ) );
echo '<a class="button" href="' . esc_url( $url ) . '">Ask about this product</a>';
}, 30 );
Your contact form needs a field that reads the product parameter. Most form plugins can do that. This works on classic templates. For block themes, add the button with a block or a shortcode in the template instead.
A contact form on the product page
Place a form under the product summary. This is easy to set up but gives you little structure: every inquiry arrives as an email, with no list to manage and no way to quote several products at once.
A quote or inquiry plugin
When enquiries become regular, a dedicated tool pays for itself. This is where Wbcom’s own plugins come in.
Woo Product Inquiry & Quote replaces the Add to cart flow with a quote request flow. Based on the plugin’s documented capabilities:
- Shoppers add products to a quote cart and submit a form with name, email, phone and a message.
- You can hide product prices and the Add to cart button store-wide, with the quote button taking their place.
- You can apply quoting to all products or to selected ones, and disable it for chosen user roles, including guests.
- Requests are stored and listed in the WordPress admin with status and date filters, bulk actions and a CSV export.
- You can send a price response email from the admin, and the plugin uses WooCommerce’s email system with templates your theme can override.
- It declares compatibility with HPOS (WooCommerce’s custom order tables).
One limit is worth knowing. The documentation marks hiding Add to cart on variable products as a known limitation, because the variation form can still render. If you sell variable products, test them, or use the plugin’s selected-products mode.
Woo Product Inquiry & Quote Pro extends the free plugin and requires it. Documented additions include:
- Extra quote statuses: approved, rejected, expired and converted.
- Automatic conversion of an approved quote into a WooCommerce order.
- Quote expiry through a daily scheduled task.
- Volume discounts and a role-based percentage discount.
- Extra form fields (company and budget), optional file uploads, and spam protection.
- Rules for which products can be quoted.
For a wider comparison of quote tools, including other vendors, see our guide to WooCommerce request a quote plugins.
When a plugin is the better choice
Code is fine for fixed rules. It becomes a burden when the rules change often or when non-developers need to manage them. Use this table to decide.
| Situation | Code snippet | Plugin |
|---|---|---|
| Whole store should stop selling, no exceptions | Good fit | Not needed |
| One category quote-only | Good fit | Optional |
| Prices hidden from guests, shown to one role | Good fit | Optional |
| Different rules per product, category, role and country | Gets messy | Better |
| You need to manage quote requests, statuses and replies | Not covered | Better |
| Several people maintain the site | Risky | Better |
| You want to switch rules on and off without editing files | Not possible | Better |
Two cautions apply to snippets. They live in a theme or a code snippet tool, so a theme change can silently remove them. Put them in a small plugin of your own instead. And they need a retest after major WooCommerce updates, because they depend on hook behavior in the core version you run.
Test before you launch
Run through this list on a staging copy, signed out and then signed in as a buyer role:
- Open a simple product, a variable product and a grouped product.
- Check the shop page, a category page and search results.
- Try to add a product by URL, using
?add-to-cart=IDwith a real product ID. - Open the cart and checkout pages directly.
- View the page source for price data in structured data.
- Open the site on a phone.
Step 3 is the one that proves the lock works. If the product is added to the cart, your filter is not covering that product type.
If your theme controls how the shop grid and product pages look, make sure the replacement call to action still fits the layout. Reign is a WooCommerce-compatible theme, so its product templates are a reasonable place to check the result.
Frequently asked questions
Does WooCommerce have a built-in catalog mode?
No. We found no catalog mode setting in WooCommerce 10.8.1. You build it from filters and actions that control purchasing and price display, as shown above, or you add a plugin.
How do I hide the Add to cart button without a plugin?
Return false from the woocommerce_is_purchasable filter. That removes the form on simple products and blocks direct add to cart links. For classic themes you can also remove the woocommerce_template_single_add_to_cart and woocommerce_template_loop_add_to_cart actions, but the filter is the more reliable layer.
How do I show prices only to logged-in users?
Check is_user_logged_in() inside callbacks on woocommerce_get_price_html and woocommerce_is_purchasable, and return an empty string or false for visitors. For wholesale buyers, test the user’s role as well.
What is the difference between the YITH catalog mode plugin and the official WooCommerce extension?
They are built for different jobs. YITH WooCommerce Catalog Mode is listed on WordPress.org and its free description centers on hiding Add to cart buttons and cart and checkout access, with an option for admins to see the store normally. The same page lists hiding prices, applying catalog mode to specific products or categories or to certain countries, button builders and an inquiry form as premium features. The official extension, Catalog Visibility Options on WooCommerce.com, centers on who can see products and prices and who can buy, with rules by user role, specific users and location. Check each vendor’s page for current features and pricing before you decide.
Will hiding prices remove them from Google?
Not automatically. The woocommerce_get_price_html filter changes the displayed HTML. Structured data and feeds may still carry price information, so inspect your page source and feeds after the change.
Can I use catalog mode for some customers only?
Yes. Put a user check, such as a login test or a role test, inside the same filters. Test with page caching on, since cached pages can show the wrong version to a visitor.