Posted on 20 Comments

How to Hide Products by User Role in WooCommerce with PHP

Sometimes you need to hide a product or make it non-purchasable depending on the visitor’s user role, for example, make all products non-purchasable for not logged users, i.e. “guests”. You can use a plugin for that or add a PHP snippet to your (child) theme’s functions.php file.

There is a number of ways we can make product non-purchasable or hide it from the shop:

In addition we can also:

First of all, we will need functions to determine if we need to hide a product for the current user’s role.

if ( ! function_exists( 'wpf_is_current_user_role' ) ) {
    /**
     * Checks current user's role. Handles multiple roles per user.
     */
    function wpf_is_current_user_role( $roles_to_check ) {
        $current_user       = wp_get_current_user();
        $current_user_roles = ( empty( $current_user->roles ) ? array( '' ) : $current_user->roles );
        $roles_intersect    = array_intersect( $current_user_roles, $roles_to_check );
        return ( ! empty( $roles_intersect ) );
    }
}
if ( ! function_exists( 'wpf_do_hide_product' ) ) {
    /**
     * Checks if the product needs to be hidden.
     */
    function wpf_do_hide_product( $product_id_to_check ) {

        // TODO: You need to replace product IDs & user roles with your own here.
        $products_to_hide  = array( 100, 150 );       // product IDs
        $roles_to_hide_for = array( '', 'customer' ); // empty string is for the "guest" user role

        // Check if the product must be hidden.
        return (
            in_array( $product_id_to_check, $products_to_hide ) && // product ID match
            wpf_is_current_user_role( $roles_to_hide_for )         // user role match
        );
    }
}

Hiding products from the catalog

To hide products from the catalog we can use woocommerce_product_is_visible filter. This will hide selected products in shop and search results. However, the products still will be accessible via direct link.

Before After
How to hide products by user role in WooCommerce - Catalog - Before How to hide products by user role in WooCommerce - Catalog - After
add_filter( 'woocommerce_product_is_visible', 'wpf_product_visible_by_user_role', PHP_INT_MAX, 2 );
if ( ! function_exists( 'wpf_product_visible_by_user_role' ) ) {
    /**
     * Hides product from shop and search results by user role.
     */
    function wpf_product_visible_by_user_role( $visible, $product_id ) {
        return ( wpf_do_hide_product( $product_id ) ? false : $visible );
    }
}

Making products non-purchasable

To make products non-purchasable we can use woocommerce_is_purchasable filter. This will make selected products non-purchasable, i.e. products can’t be added to the cart.

Before After
How to hide products by user role in WooCommerce - Purchasable - Before How to hide products by user role in WooCommerce - Purchasable - After

The algorithm is similar to hiding product in catalog, however, instead of $product_id we are passing $product->get_id().

add_filter( 'woocommerce_is_purchasable', 'wpf_product_purchasable_by_user_role', PHP_INT_MAX, 2 );
if ( ! function_exists( 'wpf_product_purchasable_by_user_role' ) ) {
    /**
     * Makes product non-purchasable by user role.
     */
    function wpf_product_purchasable_by_user_role( $purchasable, $product ) {
        return ( wpf_do_hide_product( $product->get_id() ) ? false : $purchasable );
    }
}

Removing products price

We can remove product price by user role and in addition, as there is no price, WooCommerce will make the product non-purchasable automatically.

Before After
How to hide products by user role in WooCommerce - Purchasable - Before How to hide products by user role in WooCommerce - Price - After

Again, the algorithm is similar to hiding product in catalog or making non-purchasable, however, instead of false we are returning empty string ''.

add_filter( 'woocommerce_product_get_price', 'wpf_remove_product_price_by_user_role', PHP_INT_MAX, 2 );
if ( ! function_exists( 'wpf_remove_product_price_by_user_role' ) ) {
    /**
     * Hides product price by user role.
     */
    function wpf_remove_product_price_by_user_role( $price, $product ) {
        return ( wpf_do_hide_product( $product->get_id() ) ? '' : $price );
    }
}

Hiding products by user ID instead of user role

If we need to check the exact user (instead of user role), we can use user’s ID. “Guest” will have ID at 0.

if ( ! function_exists( 'wpf_is_current_user_id' ) ) {
    /**
     * Checks current user's ID.
     */
    function wpf_is_current_user_id( $user_ids_to_check ) {
        $current_user = wp_get_current_user();
        return ( in_array( $current_user->ID, $user_ids_to_check ) );
    }
}

We’ll need to replace wpf_is_current_user_role() with wpf_is_current_user_id() in wpf_do_hide_product() function.

if ( ! function_exists( 'wpf_do_hide_product' ) ) {
    /**
     * Checks if the product needs to be hidden.
     */
    function wpf_do_hide_product( $product_id_to_check ) {

        // TODO: You need to replace product & user IDs with your own here.
        $products_to_hide  = array( 100, 150 ); // product IDs
        $users_to_hide_for = array( 10, 20 );   // user IDs

        // Check if the product must be hidden.
        return (
            in_array( $product_id_to_check, $products_to_hide ) && // product ID match
            wpf_is_current_user_id( $users_to_hide_for )           // user ID match
        );
    }
}

Hiding products by product category instead of product ID

If we need to hide product by category:

if ( ! function_exists( 'wpf_is_product_cat' ) ) {
    /**
     * Checks product's category.
     */
    function wpf_is_product_cat( $product_id, $product_cats ) {
        $current_cats = get_the_terms( $product_id, 'product_cat' );
        if ( $current_cats && ! is_wp_error( $current_cats ) ) {
            $current_cats   = wp_list_pluck( $current_cats, 'slug' );
            $cats_intersect = array_intersect( $current_cats, $product_cats );
            return ( ! empty( $cats_intersect ) );
        }
        return false;
    }
}

And in wpf_do_hide_product() function:

if ( ! function_exists( 'wpf_do_hide_product' ) ) {
    /**
     * Checks if the product needs to be hidden.
     */
    function wpf_do_hide_product( $product_id_to_check ) {

        // TODO: You need to replace product categories & user roles with your own here.
        $product_cats_to_hide = array( 'hoodies', 'hats' ); // product categories (slugs)
        $roles_to_hide_for    = array( '', 'customer' );    // empty string is for the "guest" user role

        // Check if the product must be hidden.
        return (
            wpf_is_product_cat( $product_id_to_check, $product_cats_to_hide ) && // product category
            wpf_is_current_user_role( $roles_to_hide_for )                       // user role match
        );
    }
}

20 thoughts on “How to Hide Products by User Role in WooCommerce with PHP

  1. I have an issue when I use wp_query and use wc_get_template_part( ‘content’, ‘product’ );

  2. Just perfect, thank you

  3. I tried your code but It didn’t work for me πŸ™

    I would like to HIDE specific CATEGORY for a specific USER ROLE.

    could you help please ?

    if ( ! function_exists( 'wpf_is_product_cat' ) ) {
        /**
         * Checks product's category.
         */
        function wpf_is_product_cat( $product_id, $product_cats ) {
            $current_cats = get_the_terms( $product_id, 'product_cat' );
            if ( $current_cats && ! is_wp_error( $current_cats ) ) {
                $current_cats   = wp_list_pluck( $current_cats, 'slug' );
                $cats_intersect = array_intersect( $current_cats, $product_cats );
                return ( ! empty( $cats_intersect ) );
            }
            return false;
        }
    }
    
    
    if ( ! function_exists( 'wpf_do_hide_product' ) ) {
        /**
         * Checks if the product needs to be hidden.
         */
        function wpf_do_hide_product( $product_id_to_check ) {
    
            // TODO: You need to replace product categories & user roles with your own here.
            $product_cats_to_hide = array( 'MY_CATEGORY_PRODUCT' ); // product categories (slugs)
            $roles_to_hide_for    = array( '', 'MY_CUUSTOMER_01' );    // empty string is for the "guest" user role
    
            // Check if the product must be hidden.
            return (
                wpf_is_product_cat( $product_id_to_check, $product_cats_to_hide ) && // product category
                wpf_is_current_user_role( $roles_to_hide_for )                       // user role match
            );
        }
    }
    1. Hi Tao,

      First of all, you are missing the wpf_is_current_user_role() function:

      if ( ! function_exists( 'wpf_is_current_user_role' ) ) {
          /**
           * Checks current user's role. Handles multiple roles per user.
           */
          function wpf_is_current_user_role( $roles_to_check ) {
              $current_user       = wp_get_current_user();
              $current_user_roles = ( empty( $current_user->roles ) ? array( '' ) : $current_user->roles );
              $roles_intersect    = array_intersect( $current_user_roles, $roles_to_check );
              return ( ! empty( $roles_intersect ) );
          }
      }

      Then you need to decide how you are going to hide the products. For example, to hide products from shop and search results:

      add_filter( 'woocommerce_product_is_visible', 'wpf_product_visible_by_user_role', PHP_INT_MAX, 2 );
      if ( ! function_exists( 'wpf_product_visible_by_user_role' ) ) {
          /**
           * Hides product from shop and search results by user role.
           */
          function wpf_product_visible_by_user_role( $visible, $product_id ) {
              return ( wpf_do_hide_product( $product_id ) ? false : $visible );
          }
      }

      And the last note – please make sure that MY_CATEGORY_PRODUCT and MY_CUUSTOMER_01 don’t have typos in them.

      Hope this helps. Please give it a try and let me know what you think.

  4. Hey there! Thanks so much for posting this tutorial. Unfortunately, I can’t seem to get it to work for my site..
    This is the code:

    /**
     * Woocommerce nascondi prodotti in base al ruolo utente
     */
    
    if ( ! function_exists( 'wpf_is_current_user_role' ) ) {
        /**
         * Checks current user's role. Handles multiple roles per user.
         */
        function wpf_is_current_user_role( $roles_to_check ) {
            $current_user       = wp_get_current_user();
            $current_user_roles = ( empty( $current_user->roles ) ? array( '' ) : $current_user->roles );
            $roles_intersect    = array_intersect( $current_user_roles, $roles_to_check );
            return ( ! empty( $roles_intersect ) );
        }
    }
    
    if ( ! function_exists( 'wpf_is_product_cat' ) ) {
        /**
         * Checks product's category.
         */
        function wpf_is_product_cat( $product_id, $product_cats ) {
            $current_cats = get_the_terms( $product_id, 'product_cat' );
            if ( $current_cats && ! is_wp_error( $current_cats ) ) {
                $current_cats   = wp_list_pluck( $current_cats, 'slug' );
                $cats_intersect = array_intersect( $current_cats, $product_cats );
                return ( ! empty( $cats_intersect ) );
            }
            return false;
        }
    }
    
    if ( ! function_exists( 'wpf_do_hide_product' ) ) {
        /**
         * Checks if the product needs to be hidden.
         */
        function wpf_do_hide_product( $product_id_to_check ) {
    
            // TODO: You need to replace product categories & user roles with your own here.
            $product_cats_to_hide = array( 'gdo', 'maschere' ); // product categories (slugs)
            $roles_to_hide_for    = array( '', 'customer' );    // empty string is for the "guest" user role
    
            // Check if the product must be hidden.
            return (
                wpf_is_product_cat( $product_id_to_check, $product_cats_to_hide ) && // product category
                wpf_is_current_user_role( $roles_to_hide_for )                       // user role match
            );
        }
    }
    
    add_filter( 'woocommerce_product_is_visible', 'wpf_product_visible_by_user_role', PHP_INT_MAX, 2 );
    if ( ! function_exists( 'wpf_product_visible_by_user_role' ) ) {
        /**
         * Hides product from shop and search results by user role.
         */
        function wpf_product_visible_by_user_role( $visible, $product_id ) {
            return ( wpf_do_hide_product( $product_id ) ? false : $visible );
        }
    }
    
    add_filter( 'woocommerce_is_purchasable', 'wpf_product_purchasable_by_user_role', PHP_INT_MAX, 2 );
    if ( ! function_exists( 'wpf_product_purchasable_by_user_role' ) ) {
        /**
         * Makes product non-purchasable by user role.
         */
        function wpf_product_purchasable_by_user_role( $purchasable, $product ) {
            return ( wpf_do_hide_product( $product->get_id() ) ? false : $purchasable );
        }
    }
    
    1. Hi Mario,

      I’ve just tested your code on my server, but it seems to be working fine here. If you wish, I can take a look at it on your website – you can contact me via [email protected].

  5. Hello,

    Thank you for the really useful snippets.

    I am trying to find a way to modify the non-purchasable snippet so that instead of making it non purchasable, it re-directs non authorised users back to another page. Would you have any suggestions on how would be best to go about this?

    Cheers,

    1. Hi Dan,

      Happy to help πŸ™‚

      There are probably several ways to do it. The first approach that comes to mind – you start by using our wpf_product_purchasable_by_user_role() snippet to make the selected products non-purchasable. Then you can redirect single product pages for all non-purchasable products with this snippet:

      add_action( 'wp', function() {
          if ( is_product() ) {
              $product = wc_get_product( get_the_ID() );
              if ( $product && ! $product->is_purchasable() ) {
                  wp_safe_redirect( '/your-awesome-page/' );
                  exit;
              }
          }
      } );

      I would suggest this approach because making products non-purchasable will ensure that your customers won’t add these products to the cart from the shop pages. If you don’t need that, you can simplify it by skipping the wpf_product_purchasable_by_user_role() part, and replacing

      if ( $product && ! $product->is_purchasable() ) {

      with

      if ( $product && in_array( $product->get_id(), array( 123, 456 ) ) ) {

      I.e., you simply check it by product’s ID.

      I hope this helps. Please let me know if you have any questions.

  6. Hey there! Thanks so much for posting this tutorial. Unfortunately, I can’t seem to get it to work for my site..

    What I’m trying to do is to hide 3 product categories (for example the category slugs would be: ‘hello_1’, ‘hello_2’ and ‘hello_3’) for guests and for the user role ‘personal_account’.

    This is the code that I’ve came up with:

    if ( ! function_exists( 'wpf_is_product_cat' ) ) {
        /**
         * Checks product's category.
         */
        function wpf_is_product_cat( $product_id, $product_cats ) {
            $current_cats = get_the_terms( $product_id, 'product_cat' );
            if ( $current_cats && ! is_wp_error( $current_cats ) ) {
                $current_cats   = wp_list_pluck( $current_cats, 'slug' );
                $cats_intersect = array_intersect( $current_cats, $product_cats );
                return ( ! empty( $cats_intersect ) );
            }
            return false;
        }
    }
    if ( ! function_exists( 'wpf_do_hide_product' ) ) {
        /**
         * Checks if the product needs to be hidden.
         */
        function wpf_do_hide_product( $product_id_to_check ) {
    
            // TODO: You need to replace product categories & user roles with your own here.
            $product_cats_to_hide = array( 'hello_1', 'hello_2', 'hello_3' ); // product categories (slugs)
            $roles_to_hide_for    = array( '', 'personal-account' );    // empty string is for the "guest" user role
    
            // Check if the product must be hidden.
            return (
                wpf_is_product_cat( $product_id_to_check, $product_cats_to_hide ) && // product category
                wpf_is_current_user_role( $roles_to_hide_for )                       // user role match
            );
        }
    }
    add_filter( 'woocommerce_product_is_visible', 'wpf_product_visible_by_user_role', PHP_INT_MAX, 2 );
    if ( ! function_exists( 'wpf_product_visible_by_user_role' ) ) {
        /**
         * Hides product from shop and search results by user role.
         */
        function wpf_product_visible_by_user_role( $visible, $product_id ) {
            return ( wpf_do_hide_product( $product_id ) ? false : $visible );
        }
    }
    add_filter( 'woocommerce_is_purchasable', 'wpf_product_purchasable_by_user_role', PHP_INT_MAX, 2 );
    if ( ! function_exists( 'wpf_product_purchasable_by_user_role' ) ) {
        /**
         * Makes product non-purchasable by user role.
         */
        function wpf_product_purchasable_by_user_role( $purchasable, $product ) {
            return ( wpf_do_hide_product( $product->get_id() ) ? false : $purchasable );
        }
    }
    

    I’m not very experienced in PHP so I’m not quite sure as to where I went wrong with the code. Guests and personal_account users could still see the products from category hello_1, hello_2 and hello_3. And the code that was meant to hide those categories in the catalog would end up showing the there was a serious error on your website message.

    1. Hi Ella,

      I think you forgot to add our wpf_is_current_user_role() function to your code, i.e.:

      if ( ! function_exists( 'wpf_is_current_user_role' ) ) {
          /**
           * Checks current user's role. Handles multiple roles per user.
           */
          function wpf_is_current_user_role( $roles_to_check ) {
              $current_user       = wp_get_current_user();
              $current_user_roles = ( empty( $current_user->roles ) ? array( '' ) : $current_user->roles );
              $roles_intersect    = array_intersect( $current_user_roles, $roles_to_check );
              return ( ! empty( $roles_intersect ) );
          }
      }

      Please give it a try and let me know if that helps.

  7. Hello and thank you very much for this very useful post!
    I have a question:
    – if I would need to hide some products only for customers (i.e. product ids 54, 62) and to hide some other products only for guests (i.e. product ids 32, 36), what do you suggest?
    Thank you very much!
    Riccardo

    1. Hi Riccardo,

      Thank you πŸ™‚

      You’ll need to modify our wpf_do_hide_product() function. Like this:

      if ( ! function_exists( 'wpf_do_hide_product' ) ) {
          /**
           * Checks if the product needs to be hidden.
           */
          function wpf_do_hide_product( $product_id_to_check ) {
              return (
                  // Customers
                  ( in_array( $product_id_to_check, array( 54, 62 ) ) &&
                  wpf_is_current_user_role( array( 'customer' ) ) ) ||
                  // Guests
                  ( in_array( $product_id_to_check, array( 32, 36 ) ) &&
                  wpf_is_current_user_role( array( '' ) ) )
              );
          }
      }

      Please give it a try and let me know if you have any questions.

    2. Thank you very much Tom! I really appreciate!

      It works but I still have a problem with the view.

      I have to hide some products for guests and other products for logged in user in the same category.
      The code actually hides the products properly according to the user role but the products are still in the category page even if the user can’t see them and they ara counted in the pages.

      Let me explain:
      – I have 100 products in the category “CATEGORY1”
      – I need to hide 50 products (ID from 1 to 50) for logged-in clients and show them only products with Id from 51 to 100
      – I need to hide 50 products (ID from 51 to 10) for guests and show them only products with Id from 1 to 50.

      When I apply the code, the user can’t see the products but (let’s say default settings for the view is to show 50 products per page):

      – The logged-in client (that will see only products from id 51 to 100), when opens the category he sees a blank page with the page count on the bottom showing the numbers of page (1-2).
      To see the products he has to click on page 2 and see the products (IDs 51-100).

      – At the same time, a guest sees page 1 with the products (ID 1-51) and when he clicks on page 2 he sees a blank page (hidden products for guests IDs 51-100).

      The problem is that the client will see an empty page when he should see his products without browsing pages to find them.

      Any suggestion?
      Thank you very very much.
      Riccardo

    3. Hi Riccardo,

      Ok, let’s try a bit different approach, and modify woocommerce_product_query instead. Here is the new code, please give it a try and let me know if it’s working as expected:

      add_action( 'woocommerce_product_query', 'wpfactory_wc_product_query', PHP_INT_MAX );
      if ( ! function_exists( 'wpfactory_wc_product_query' ) ) {
          /**
           * Hides products by modifying `woocommerce_product_query`.
           */
          function wpfactory_wc_product_query( $query ) {
      
              // TODO: You need to replace product IDs & user roles with your own here
              $products_to_hide = array(
      
                  'customer' => array(
                      54,
                      62,
                  ),
      
                  '' => array( // "Guest" user role
                      32,
                      36,
                  ),
      
              );
      
              // Get current user roles
              $user  = wp_get_current_user();
              $roles = empty( $user->roles ) ? array( '' ) : $user->roles;
      
              // Get hidden product IDs
              $hidden_products = array();
              foreach ( $roles as $role ) {
                  if ( ! empty( $products_to_hide[ $role ] ) ) {
                      $hidden_products = array_merge( $hidden_products, $products_to_hide[ $role ] );
                  }
              }
      
              // Hide products
              if ( ! empty( $hidden_products ) ) {
      
                  // Get current query `post__not_in`
                  $post__not_in = $query->get( 'post__not_in' );
                  $post__not_in = empty( $post__not_in ) ? array() : $post__not_in;
      
                  // Add our hidden products
                  $post__not_in = array_merge( $post__not_in, $hidden_products );
      
                  // Set query `post__not_in`
                  $query->set( 'post__not_in', array_unique( $post__not_in ) );
      
              }
      
          }
      }
  8. Thank you, I had to change this

    $current_user_roles = ( empty( $current_user->roles ) ? array( '' ) : $current_user->roles );
    $roles_intersect    = array_intersect( $current_user_roles, $roles_to_check );

    to this

    $current_user_roles = $current_user->roles;

    to work for me, still figuring out why?

    1. Hi Ramses,

      I assume you are referring only to the differences in the first line, i.e. you are still calling the second line:

      $roles_intersect = array_intersect( $current_user_roles, $roles_to_check );

      If that’s correct – the difference between our code and yours only affects not logged users, i.e. “guests”.

      Your code will return an empty array for the roles property, i.e. for the guests, this:

      $current_user_roles = $current_user->roles;

      will result in this:

      $current_user_roles = array();

      Our code will return an array with an empty string element in it, i.e. for the guests, this:

      $current_user_roles = ( empty( $current_user->roles ) ? array( '' ) : $current_user->roles );

      will result in this:

      $current_user_roles = array( '' );

      So when we are checking if the current user’s role is a “guest”, we are checking if an empty string is present in the roles array. And in your case, you should check if the current user’s roles array is empty instead.

      So to sum up – basically it’s possible to make it work as you did, however, the code will be different when checking for the “guest” role.

      Hope that helps. And please let me know if you have any questions.

  9. Hi when I hide a lots of products in the shop pages there are a lot of blank spaces, it seems that the shop count the hidden products.
    What I have to do?

    1. Hi Pietro,

      I’ve just re-tested it on my server, but it seems to be working fine here – there are no blank spaces instead of the products. Which theme do you have installed on your site?

  10. Great article, nice and clear. It really helped me out!

Leave a Reply

Your email address will not be published.