Modifying the WooCommerce Product Query

UPDATE 9/28/2019: Example 2 no longer works as WooCommerce now links the product in a grouped product via meta data instead of via post_parent.

I recently answered a few questions on Stack Overflow and they all seemed to related to how to change what products show up in the shop loop… or any other of the shop archives. I figured I would compile them altogether for reference and inspiration.

WooCommerce builds a custom query for products in its WC_Query class by hooking into the classic pre_get_posts hook and changing WordPress’s query parameters to get the desired products. WooCommerce then removes itself from the query afterwards. I’m not 100% sure of why, but I presume there is a good reason. It might be running in WooCommerce, but it is still a regular WordPress query and so the regular WP_Query Parameters apply.

Like most WooCommerce code there is a convenient action hook right in the middle of the product_query() method which will allow us to hook in and make whatever changes we might like to the product query. Note that $q is the query and $this is the WC_Query class instance.

do_action( 'woocommerce_product_query', $q, $this );Code language: PHP (php)

Example 1: Display Only On-Sale products in Shop

WooCommerce has a shortcode for displaying the most recent on-sale products. So I took a look at how it sets up it’s query args for get_posts(). Turns out WC has a built-in function that returns the IDs of any on-sale products: wc_get_product_ids_on_sale(). So we get grab those ids and then query for those specific posts using the post__in parameter of WP_Query.

function so_20990199_product_query( $q ){ 
	$product_ids_on_sale = wc_get_product_ids_on_sale(); 
	$q->set( 'post__in', (array) $product_ids_on_sale ); 
}
add_action( 'woocommerce_product_query', 'so_20990199_product_query' );Code language: PHP (php)

As is typical in dealing with WordPress query objects we can use get() and set() methods to retrieve info about the query or set new parameters.

Example 2: Hide Products that are Part of a Grouped Product

The person who asked this question has a lot of products that part of other grouped products and didn’t want to display the items individually and in their grouped versions. Turns out that items that are part of a group have the grouped product as their post_parent. Any grouped product (or other top-level, non-grouped product) will have 0 as a post parent. Therefore, we can set the query argument to require a post parent of 0 and effectively eliminate any grouped items.

function so_27975262_product_query( $q ){ 
	$q->set( 'post_parent', 0 ); 
}
add_action( 'woocommerce_product_query', 'so_27975262_product_query' ); Code language: PHP (php)

Example 3: Hide Specific Out of Stock Items

WooCommerce has a setting that allows you to hide all out of stock items from your store. But what if you don’t want a nuclear option and want to have a little finer control over which items are going to be hidden.

We can’t skip straight to the query, because WooCommerce doesn’t have this data. So we need to add some meta. You could use a custom field, but that isn’t as friendly as adding a little checkbox to the product data metabox. I thought it would be appropriate if we placed it near the stock status inputs. WooCommerce already has functions for creating most of the standard input types so we’ll use that to our advantage.

function so_27971630_hide_if_out_of_stock() {
	woocommerce_wp_checkbox( 
		array( 'id' => '_hide_if_out_of_stock',
				'wrapper_class' => 'show_if_simple show_if_variable',
				'label' => __( 'Hide this product from archives when out of stock?', 'your-plugin-domain' )
			)
	); 
}
add_action( 'woocommerce_product_options_stock_status', 'so_27971630_hide_if_out_of_stock' );Code language: PHP (php)

Then we need to save this data. Normally, I’d save a checkbox as ‘yes’ versus ‘no’ like WooCommerce does. However, getting the product query correct (as you’ll see a little later on), required that the meta exist when you wanted to hide the item and not exist at all otherwise… hence the if/else update_post_meta() versus delete_post_meta()

function so_27971630_save_product_meta( $product ) {
	if( isset( $_POST['_hide_if_out_of_stock'] ) ) { 
		$product->update_meta_data( '_hide_if_out_of_stock', 'yes' ); 
	} else { 
		$product->delete_meta_data( '_hide_if_out_of_stock' );
	}
}
add_action( 'woocommerce_admin_process_product_object', 'so_27971630_save_product_meta' );Code language: PHP (php)

And now we can get to the query. Because WooCommerce already has a few keys in its default meta query and the default meta query operator is AND (meaning my conditions had to be met in addition to what WooCommerce was already looking for), I couldn’t figure out a way to query for posts that had meta equal to a specific key. But because WordPress supports EXISTS and NOT EXISTS comparisons, we can look for posts that way. What I’ve done is in the case where you aren’t mass hiding all out of stock items via the plugin option, this code will modify the meta query so that any item that does not have the meta key _hide_if_out_of_stock will be shown. That is a counter-intuitive way of saying that any product where the box “hide when out of stock” is checked will be hidden.

function so_27971630_product_query( $q ) {
	$meta_query = $q->get( 'meta_query' ); 
	if ( get_option( 'woocommerce_hide_out_of_stock_items' ) == 'no' ) { 
		$meta_query[] = array( 'key' => '_hide_if_out_of_stock', 'compare' => 'NOT EXISTS' );
		$q->set( 'meta_query', $meta_query );
	}
}
add_action( 'woocommerce_product_query', 'so_27971630_product_query' );Code language: PHP (php)

Example 4: Hide Products of Specific Category

Technically, Woo has a “catalog visibility” setting in the publish metabox so you can hide individual products from the shop loop. And it’s done via taxonomy. But occasionally, you can run into a need to hide things by your own taxonomy:

function kia_hide_tax_from_product_query( $q ) {
	$tax_query = $q->get( 'tax_query' );

	$tax_query[] = array(
		'taxonomy' => 'display_mode',
        'field'    => 'slug',
        'terms'    => array( 'catalog' ),
        'operator' => 'NOT',
	);
	$q->set( 'tax_query', $tax_query );

}
add_action( 'woocommerce_product_query', 'kia_hide_tax_from_product_query' );Code language: PHP (php)

Those are the uses of modifying WooCommerce’s product query that I have encountered in the past few days. Anybody else doing anything interesting to the product query?

8 thoughts on “Modifying the WooCommerce Product Query

  1. Steve

    Thanks! Just what I needed. I’m working on custom sorting WooCommerce results by ACF fields. Solution based on this below if anyone doing the same is interested. Can’t seem to find a better place to post it.

    add_action( 'woocommerce_product_query', 'so_27971630_product_query' );
    
    function so_27971630_product_query( $q ){
        $meta_query = $q->get( 'meta_query' );
        $meta_query[] = array(
            'key' => 'custom_acf_key',
            'value' => 'custom_acf_value',
            'compare' => '='
            );
    
        $q->set( 'meta_query', $meta_query );
    }
    
    1. kathy Post author

      Thanks for sharing that! I probably should have made an example of just a plain meta query, but now I don’t have to.

  2. Shweta

    Hi, I am using woocommerce and m trying to make all out of stock products come after Instock products in category page. Can you please help me with this.

    Thanks in Advance

    1. kathy Post author

      Hi @Asaduzzaman, I can confirm that #1 still works. Example 2 no longer works as WooCommerce now links the product in a grouped product via meta data instead of via post_parent. And #3 should work again after a small tweak.

  3. Yoni

    Thanks a lot for the guide, very useful.
    Can you maybe explain for wp beginner developer how to actually do configure this queries in woocommerce admin or backend code?

    1. kathy Post author

      Hi Yoni. Is there a specific question about a particular query you are trying to create? But since products are a custom post type they will behave like posts and so can be queried in mostly the same way. To start digging in to how the WordPress query system works I suggest starting with this page on WP_Query.

Comments are closed.