I suspect that if you’ve ever had to create a custom WooCommerce Product loop that you probably used
get_posts() or even a
WP_Query(). Did you know that could actually stop working unexpectedly? If you didn’t know that then I’m guessing you also didn’t know that WooCommerce actually has a native function for doing this. It’s ok, I didn’t know any of that either until I wanted to create a custom WooCommerce Product loop complete with all its addons and paging without repeating any code.
On a recent WooCommerce project I was tasked with building a page to display only Featured Products. WooCommerce actually has a handy function to make getting Featured Product IDs very quick,
wc_get_featured_product_ids(). Using that, it’s fairly trivial to plug that in to a custom
WP_Query() and fire out a vanilla custom loop (or use
get_posts() if you’re determined to do it the long way). I could even use the Featured Products shortcode, but those methods aren’t ideal. Unfortunately, anyone searching for the right way to accomplish this will find no shortage of stackexchange posts offering
get_posts() solutions. Let’s talk about why those aren’t great and what the best practices are.
The Downsides of using WP_Query & Shortcodes
The downside to a custom WooCommerce loop using any of the methods above is that I’d end up losing the WooCommerce goodies that are baked into WooCommerce’s native loop. I wouldn’t get a nifty ordering dropdown to sort products by various criteria, and I wouldn’t get my much needed pagination. I can add those elements back into the loop myself, but it’s a good amount of repetitive code I’d like to avoid adding to the project, especially because WooCommerce already comes with that code. In addition, if I’ve used any of the WooCommerce action or filter hooks to change the main WooCommerce loop behavior or appearance then I’ll need to account for those changes in my custom loop.
One more important downside is mentioned in the WooCommerce GitHub Wiki (emphasis added):
Building custom WP_Queries or database queries is likely to break your code in future versions of WooCommerce as data moves towards custom tables for better performance. This is the best-practices way for plugin and theme developers to retrieve multiple products.
After reading that and considering the other downsides of using
WP_Query() and other methods to create a custom WooCommerce product loop, I decided to dig around and devise the most Woo-native way of creating a custom product loop to keep all the WooCommerce loop features while still leaving room to make the customizations I need. In my case, and the example I’ll be demonstrating, that means displaying only Featured Products on a page made to closely resemble the main Shop page.
A Better Way
My quest for a better WooCommerce custom loop started where all great copies start: the original. So I took a look at
archive-product.php inside my WooCommerce installation and began analyzing it. It’s actually very simple, but it’s also very nuanced. So I’m going to follow the structure of the WooCommerce Archive Template, including its action hooks and filters (along with a few other important bits I’ll need to add), but instead of using the WordPress Archive loop (which isn’t available in a standard page template) I’ll use
wc_get_products() to follow best practices mentioned in the Wiki above.
Here’s what we’re starting with:
This is the simple base we’re starting with. It’s a very basic WordPress loop with a few action hooks and WooCommerce functions. Let’s dig in to how we can replicate this with our custom loop.
The first thing I did was make a Page Template. If you found this post then I assume you already know how to do that or you can use that link to figure it out. Next, I’ll copy the entire loop from the WooCommerce Product Archive template. However, I need to make some adjustments because that loop relies on the WordPress main Post Type Archive query. That query won’t be available on a Page (hence the need for a custom query/loop).
The Custom WooCommerce Product Loop
I’ll be using
wc_get_products() as recommended on the WooCommerce Wiki so I need to adjust my code because that function (and even the WC_Product_Query Class) doesn’t give me the methods we’re all used to when creating custom loops, namely
have_posts(). Instead of relying on
the_post(), I’ll create a
foreach loop. My project’s main Shop Page makes use of the WooCommerce ordering dropdown and pagination, so I’ll need to get some of the relevant variables to ensure those functions work here, and I also need to set some properties in the WooCommerce loop variable (
$GLOBAL['woocommerce_loop']). Here’s what I came up with:
It’s actually fairly straightforward. First I get and set the variables for paging and order display in lines 7-11, then I get my Products, then I hack the WooCommerce loop variable with some parameters it needs in lines 25-30, then I finally loop through them using a
foreach loop instead of the usual
while loop because I don’t have the Loop methods to rely on as mentioned above. Lines 36-38 use the same form and function to temporarily set the global Post object as WooCommerce uses in its shortcodes, but hit the comments if there’s a better way.
Creating a custom WooCommerce Product loop on a Page in WordPress is often mentioned in tutorials and almost always calls for using a new
get_posts(). Both of those methods are certainly viable, but ultimately they may not keep pace with WooCommerce updates and so the right way to create a custom product loop is to use
wc_get_products() and a
foreach loop or
WC_Product_Query(). It’s also possible through a few quick tricks to persuade WooCommerce into outputting its goodies like ordering dropdowns and pagination to save you some time and save your project from repetitive code. This method can be used just about anywhere in your templates and does a thorough job of outputting a WooCommerce Product loop that matches up with the default Product loop on your Shop and Product Category pages. Any changes you’ve made to the WooCommerce loop’s action hooks or filters will be output when using this snippet so you won’t need to repeat yourself.
I hope this helps more WordPress and WooCommerce developers implement custom Product loops the right way. If you have questions or comments or noticed something I may have overlooked please leave a comment below!