Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Filter products by price range using WooCommerce wc_get_products

Tags:

woocommerce

I'm getting a bunch of paginated variable products using wc_get_products(). And I would like to only get products in a given price-range. The problem is that there are a lot of products, so only 20 products are fetched at a time.

According to the documentation, there are no meta_query()-option when using wc_get_products().

So how do I get this?

Here is my current query:

$products_query = [
    'category' => [ get_queried_object()->slug ],
    'limit' => 20,
    'paginate' => true,
    'paged' => (get_query_var('paged')) ? get_query_var('paged') : 1,
];

$products = wc_get_products( $products_query );

Now remember, that it's something that needs to be implemented before the products are queried from the database. Let's say that I have 100 products total. Since the products are paginated, then if I have ordered the products after _price (ASC), then page 1 should return the 20 cheapest products. And page 3 should return the products 40-60 (after they have been sorted).


Solution attempts

Attempt 1 - Try using a meta_query anyways

I figured that wc_get_products was inspired and built on top of WP_Query, so I tried this:

$products_query = [
    'category' => [ get_queried_object()->slug ],
    'limit' => 20,
    'paginate' => true,
    'paged' => (get_query_var('paged')) ? get_query_var('paged') : 1,
    'meta_query' => [
        'relation' => 'AND',
        [
            'key' => '_price',
            'value' => 100,
            'compare' => '<=',
            'type' => 'NUMERIC'
        ]
    ]
];

$products = wc_get_products( $products_query );

It just ignores the meta_query-part and returns a result as if it wasn't there.


Attempt 2 - Using WP_Query

Everywhere I look, there are arrows pointing towards wc_get_products and away from wp_query. So I haven't pursued this.


Attempt 3 - Raw SQL

One solution would be to make a raw SQL-query. But since the products are variable products, then the SQL-query would be quite a few joins, since it should first find all products - and then find all variations. And then sort the products after the lowest variation-price (if the price-sort is ascending) - and after the highest variation-price (if the price-sort is descending). It's obviously possible, but I was hoping to find a more WordPress-like solution. And I'm not good at SQL.


Attempt 4 - Using a plugin

I look around a bit - and sort'n'filter-plugins for WooCommerce are monsters, since they slab scripts and styles left, right and center. So this seems like a poor solution.


Attempt 5 - Using WooCommerce API

I can see that min_price and max_price are mentioned under List all products. So this might actually work.

like image 655
Zeth Avatar asked Oct 21 '25 06:10

Zeth


1 Answers

Variable products are something complicated to filter by price… So the following is not perfect, but show you a way to enable a price range in WC_Product_Query.

So the following function will enable a custom price range query on WC_Product_Query:

add_filter( 'woocommerce_product_data_store_cpt_get_products_query', 'handle_price_range_query_var', 10, 2 );
function handle_price_range_query_var( $query, $query_vars ) {
    if ( ! empty( $query_vars['price_range'] ) ) {
        $price_range = explode( '|', esc_attr($query_vars['price_range']) );

        if ( is_array($price_range) && count($price_range) == 2 ) {
            $query['meta_query']['relation'] = 'AND';

            $query['meta_query'][] = array(
                'key'     => '_price',
                'value'   => reset($price_range), // From price value
                'compare' => '>=',
                'type'    => 'NUMERIC'
            );

            $query['meta_query'][] = array(
                'key'     => '_price',
                'value'   => end($price_range), // To price value
                'compare' => '<=',
                'type'    => 'NUMERIC'
            );

            $query['orderby'] = 'meta_value_num'; // sort by price
            $query['order'] = 'ASC'; // In ascending order
        }
    }
    return $query;
}

Code goes in functions.php file of the child theme (or in a plugin). Tested and works.

EXAMPLE USAGE

  • Handle float numbers.
  • The 1st price is separated from the 2nd one by a pipe |

Here we query products from $10.25 to $50 (price range):

$products = wc_get_products( [
    'limit'       => 20,
    'status'      => 'publish',
    'price_range' => '10.25|50', // The first price is separated from the 2nd one with a pipe
] );

echo '<ul>';

foreach( $products as $product ) {
    echo '<li><a href="'.$product->get_permalink().'">'.$product->get_name().' '.$product->get_price_html().'</a></li>';
}
echo '</ul>';

To filter only variable products, you can add the following line to the query:

    'type'        => 'variable',

Documentation: wc_get_products and WC_Product_Query


Optimized code version:

(thanks to @ChadReitsma)

add_filter( 'woocommerce_product_data_store_cpt_get_products_query', 'handle_price_range_query_var', 10, 2 );
function handle_price_range_query_var( $query, $query_vars ) {
    if ( ! empty( $query_vars['price_range'] ) ) {
        $price_range = explode( '|', esc_attr($query_vars['price_range']) );

        if ( is_array($price_range) && count($price_range) == 2 ) {
            $query['meta_query'][] = array(
                'key'     => '_price',
                'value'   => $price_range, // Price range array
                'compare' => 'BETWEEN'
                'type'    => 'NUMERIC'
            );
            $query['orderby'] = 'meta_value_num'; // sort by price
            $query['order'] = 'ASC'; // In ascending order
        }
    }
    return $query;
}
like image 127
LoicTheAztec Avatar answered Oct 25 '25 05:10

LoicTheAztec