I have a client that is using WooCommerce subscriptions as a way to create custom payment plans for individual customers of theirs. This is because the payment plans are often personalized to the customer’s personal financial situation. While WooCommerce may not be an obvious choice of software for this, it can be easily leveraged for this. The main benefits for this is that A) they already have a WooCommerce Membership website and so it’s a system they are already familiar with, and B) Doing it in a consolidated system through their WordPress website gives them a consolidated financial reporting dashboard.
The main problem with this is that these password-protected products were still showing up in the “related products” section on their individual product page template. This information shouldn’t be shared and public, because it’s awkward and wrong to display that other customers are potentially paying a different amount that you are.
WooCommerce has a built in option for catalog visibility, but this only affects the WC shop result pages. So the individual product pages, as well as the general full site search results page, were still showing these personalized and confidential payment plan products.

Here’s some custom functions I wrote that you can drop in your theme’s function.php file, or in a custom functionality plugin (my preference):
<?php
// prevent password protected products from showing up in the search results
// make this run only on the front end of the website
if ( ! is_admin() ) {
add_filter( 'posts_where', 'exclude_password_protected_posts' );
}
function exclude_password_protected_posts( $where ) {
global $wpdb;
if ( is_search() ) {
$where .= " AND {$wpdb->posts}.post_password = '' ";
}
return $where;
}
//woocommerce_related_products filter: This filter is used to exclude password-protected products from the list of related products. It checks for password-protected products using the get_posts function and removes them from the array of related products.
add_filter( 'woocommerce_related_products', 'exclude_password_protected_products_from_related', 10, 3 );
function exclude_password_protected_products_from_related( $related_posts, $product_id, $args ) {
$password_protected_posts = get_posts( array(
'post_type' => 'product',
'post_status' => 'publish',
'posts_per_page' => -1,
'post__in' => $related_posts,
'has_password' => true,
) );
if ( ! empty( $password_protected_posts ) ) {
foreach ( $password_protected_posts as $password_protected_post ) {
$index = array_search( $password_protected_post->ID, $related_posts );
if ( $index !== false ) {
unset( $related_posts[ $index ] );
}
}
}
return $related_posts;
}
//woocommerce_upsell_display_args filter: This filter is used to exclude password-protected products from the list of upsells. It sets the exclude_password_protected argument to true.
add_filter( 'woocommerce_upsell_display_args', 'exclude_password_protected_products_from_upsell', 10, 1 );
function exclude_password_protected_products_from_upsell( $args ) {
$args['exclude_password_protected'] = true;
return $args;
}
//woocommerce_cross_sells_products filter: This filter is used to exclude password-protected products from the list of cross-sells. It checks for password-protected products using the get_posts function and removes them from the array of cross-sells.
add_filter( 'woocommerce_cross_sells_products', 'exclude_password_protected_products_from_cross_sell', 10, 1 );
function exclude_password_protected_products_from_cross_sell( $cross_sells ) {
$password_protected_posts = get_posts( array(
'post_type' => 'product',
'post_status' => 'publish',
'posts_per_page' => -1,
'post__in' => $cross_sells,
'has_password' => true,
) );
if ( ! empty( $password_protected_posts ) ) {
foreach ( $password_protected_posts as $password_protected_post ) {
$index = array_search( $password_protected_post->ID, $cross_sells );
if ( $index !== false ) {
unset( $cross_sells[ $index ] );
}
}
}
return $cross_sells;
}
You likely also want to hide these products from been indexed by Google and does showing up in Google search results, so you can add this additional function to hide them automatically. The other alternative is doing so on a case-by-case basis, through tools such as Yoast SEO.
// add a "noindex" metatag for all pages and products that are designated as password protected
add_action( 'wp_head', 'add_noindex_to_password_protected_pages' );
function add_noindex_to_password_protected_pages() {
if ( post_password_required() ) {
echo '<meta name="robots" content="noindex" />';
}
}