I'm trying to alter the Woocommerce product archive loop to make it different than the basic structure. Currently if I set the archive page to display categories and products, it will just show them all in the same grid. What I need is to display the category title above the products it contains. I will try to make my intentions visual.

Shop grid by default:

===== Shop =====       c = category, p = product
    c c p p
    p p p p

What I need is to display the categories like so:

===== Shop =====
 Category title
    p p p p
    p p p p
 Category title
    p p p p
    p p p p

In the Woocommerce template archive-product.php the following lines create the basic layout:



          while ( have_posts() ) : the_post();

            wc_get_template_part( 'content', 'product' );

          endwhile; // end of the loop.


I believe the changes need to be made there, but there don't seem to be any template files to go into the subcategories any deeper. I think I know of a way to create custom queries in there, but it seems a little messy to use that.

So my question is basically if there is a 'proper' way to achieve the result I need? Or any tips of how to approach it are welcome!


I think the subcategory thing get generated with this function woocommerce_product_subcategories();. But as you said for this layout there is no specific template file. Also, if I remember right, you cannot do this with css alone.

A little while ago I created a little plugin for a customer who also wanted a product overview structured by the product categories like you want to have.

The plugin-way is one way.

The good thing with a plugin solution is that you can show your category/products list everywhere where you can add shortcodes. (post, page, widgets) And you can also add arguments if you just want to show a single category with products or dont want to show count or description. I also made my list collapsable, so that a customer can collapse single categories only and can hide others.

The other way, as you already mentioned, is editing the archiv-product.php way.

To do this, first copy the archiv-product.php from the woocommerce plugin directory into your theme directory inside a new folder called woocommerce. After this, you can edit the archiv-product.php file inside your theme directory.

With both ways you than need some logic like this: (tried my best to comment the code as we go along)

// first we need to get the product category terms ....

$categories_args = array(
    'taxonomy' => 'product_cat' // the taxonomy we want to get terms of
    //'parent' => 0, // only show terms if parent is 0 = top level terms, try it

$product_categories = get_terms( $categories_args ); // get all terms of the product category taxonomy

if ($product_categories) { // only start if there are some terms

    echo '<ul class="catalog">';

    // now we are looping over each term
    foreach ($product_categories as $product_category) {

        $term_id    = $product_category->term_id; // single term ID
        $term_name  = $product_category->name; // single term name
        $term_desc  = $product_category->description; // single term description
        $term_link  = get_term_link($product_category->slug, $product_category->taxonomy); // single term link

        echo '<li class="product-cat-'.$term_id.'">'; // for each term we will create one list element

        echo '<h4 class="product-cat-title"><a href="'.$term_link.'">'.$term_name.'</a></h4>'; // display term name with link

        echo '<p class="product-cat-description">'.$term_desc .'</p>'; // display term description

        // ... now we will get the products which have that term assigned...

        $products_args = array(
            'post_type'     => 'product', // we want to get products
            'tax_query'     => array(
                    'taxonomy' => 'product_cat', // the product taxonomy
                    'field'    => 'term_id', // we want to use the term_id not slug
                    'terms'    => $term_id, // here we enter the ID of the current term *this is where the magic happens*

        $products = new WP_Query( $products_args );

        if ( $products->have_posts() ) { // only start if we hace some products

            // START some normal woocommerce loop, as you already posted in your question

            woocommerce_product_loop_start(); // this will generate the start of the default products list UL

            while ( $products->have_posts() ) : $products->the_post();

                wc_get_template_part( 'content', 'product' ); // we are using the default product template from woocommerce

            endwhile; // end of the loop.

            woocommerce_product_loop_end(); // this generates the end of the default woocommerce product list UL

            // END the normal woocommerce loop

            // Restore original post data, maybe not needed here (in a plugin it might be necessary)

        } else { // if we have no products, show the default woocommerce no-product loop

            // no posts found
            wc_get_template( 'loop/no-products-found.php' );

        }//END if $products

        echo '</li>';//END here is the end of our product-cat-term_id list item

    }//END foreach $product_categories

    echo '</ul>';//END of catalog list

}//END if $product_categories

The thing is you cant just replace the normal woo loop (code you posted) with this code snippet above. You will also need to delete some other lines in the archiv-product.php file!

I created a gist of a fully working archiv-product.php file here. I think this will be helpful to you.

Also look at get_terms() function here, and WP-Query arguments here. I just used the basic arguments that we need, surely you will need some more arguments.

  • This is almost exactly what I needed, thank you for the very detailed answer! Your gist is very helpful and it worked perfectly! I am now altering it to my needs, since in my structure I need it to only show the main categories at the first page. – Pascal Feb 22 '17 at 21:10
  • @pascal I think you should be able to achieve this if you use 'parent' => 0 in the get_terms() function. Will edit the answer. – LWS-Mo Feb 24 '17 at 10:44
  • @LWS-Mo Please help me, you did a great job by posting this, can you please help me. When i pasted your code on archive-product.php it worked but it pulled all categories and sub categories on the website. Can you tell me how to pull current category and sub-category? It will help alot – Nimesh Jun 18 '18 at 3:27
  • @Nimesh I think you should start your own question. – LWS-Mo Jun 18 '18 at 10:34
  • @Nimesh Sorry I took to long for the comment to edit ;) I suggest you start your own question, because this is more complicated. Maybe for a start; first you need to look into conditional statements like is_shop() and is_product_category(). With this you can use get_queried_object(); on category pages to get the current active term-object. Than you can see if this term has a parent or not (e.g. if its a subcategory or not). Maybe you need two different queries now. – LWS-Mo Jun 18 '18 at 10:41

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.