Your query syntax looked good, although you could instead add a direct meta/taxonomy query clause array such as $meta_query[] = array( 'key' => '_price', ... )
instead of $meta_query[] = array( 'relation' => 'AND', array( 'key' => '_price', ... ) )
.
However, I spotted 2 main issues with your code:
Issue 1: Your custom pagination function will not work correctly with secondary WP_Query
queries.
Because the function (wordpress_numeric_post_nav
) was intended only for the main WordPress query which is referenced using the global $wp_query
object.
So if you want that function to work with secondary or custom WP_Query
queries, then you need to adjust your function so that it supports custom WP_Query
instances like your $products
object.
Here’s a simple modification you can try:
-
Change the
function wordpress_numeric_post_nav()
to:function wordpress_numeric_post_nav( $wp_query = null )
-
Then in that function, change the
global $wp_query;
to:if ( ! is_a( $wp_query, 'WP_Query' ) ) { global $wp_query; }
So with that, you’ll display the pagination like so:
echo wordpress_numeric_post_nav( $products );
Issue 2: You should use the pre_get_posts
action and not creating a secondary WP_Query
query and loop.
The main query also uses paged
as the pagination query var (which is read from the URL of, or matching rewrite rule for the current page), hence it will conflict with the pagination for secondary WP_Query
queries on archive pages like your product
post type archive, because the main query could have less number of pages than secondary queries, leading to 404 errors.
So although 404 error isn’t or may not be the problem here, I strongly suggest you to use the pre_get_posts
action to apply your custom meta and taxonomy filters.
So just remove that $products = new WP_Query($args);
(and the variables, etc. used for that piece of code) from your template (archive-product.php
) and then:
-
In the theme functions file, add this snippet:
add_action( 'pre_get_posts', 'my_product_archive_pre_get_posts' ); function my_product_archive_pre_get_posts( $query ) { // Checks whether we are on the product archive page and that the current // WP_Query instance is the main query. We also don't want to affect admin // posts queries, hence we do `! is_admin()` check. if ( ! is_admin() && is_post_type_archive( 'product' ) && $query->is_main_query() ) { $tax_query = array( 'relation' => 'AND' ); $meta_query = array( 'relation' => 'AND' ); // Sorts the posts by the stock status and then price. $meta_query[] = array( 'relation' => 'AND', '_stock_status' => array( 'key' => '_stock_status', 'compare' => 'EXISTS', ), '_price' => array( 'key' => '_price', 'compare' => 'EXISTS', ), ); // Filter by a specific price range. if ( ! empty( $_GET['product-price'] ) ) { $range = sanitize_text_field( $_GET['product-price'] ); $prices = array_map( 'intval', explode( ':', $range ) ); $meta_query[] = array( 'key' => '_price', 'value' => $prices, 'type' => 'NUMERIC', 'compare' => 'BETWEEN', ); } // Filter by a specific product type. if ( ! empty( $_GET['product-type'] ) ) { $tax_query[] = array( 'taxonomy' => 'product_cat', 'field' => 'slug', 'terms' => sanitize_text_field( $_GET['product-type'] ), ); } // Filter by a specific product brand. if ( ! empty( $_GET['product-brand'] ) ) { $tax_query[] = array( 'taxonomy' => 'pa_brands', 'field' => 'slug', 'terms' => sanitize_text_field( $_GET['product-brand'] ), ); } // Now modify the main query's arguments. $query->set( 'posts_per_page', 16 ); $query->set( 'tax_query', $tax_query ); $query->set( 'meta_query', $meta_query ); $query->set( 'orderby', array( '_stock_status' => 'ASC', '_price' => 'DESC', ) ); } }
Hint: You can actually just use the
'<taxonomy>' => '<term slug>'
format, hence you can remove the$tax_query
parts and do something like:// Filter by a specific product type. if ( ! empty( $_GET['product-type'] ) ) { $query->set( 'product_cat', sanitize_text_field( $_GET['product-type'] ) ); } // Filter by a specific product brand. if ( ! empty( $_GET['product-brand'] ) ) { $query->set( 'pa_brands', sanitize_text_field( $_GET['product-brand'] ) ); }
-
In your template, replace the
$products->have_posts()
withhave_posts()
and then the$products->the_post()
withthe_post()
, i.e. just use the main loop to display the posts.
That way, you would not need to modify your pagination function and simply use echo wordpress_numeric_post_nav();
to display the pagination.
Additional Notes
-
On singular pages like a Page (post type
page
), your secondaryWP_Query
query and loop, as well as the pagination, would work properly. (Because the main query does not usepaged
) -
WordPress core has built-in pagination functions like
the_posts_pagination()
andpaginate_links()
. -
You can learn more about the main (WordPress) query at https://codex.wordpress.org/Query_Overview.
Sorry, I could not find an updated version of the above article on the WordPress Developer Resources site..