We often get the question of how to order a query by taxonomy terms or categories. With the bonus question of how to add a term heading above each group of posts sharing a term/category.

The orderby parameter in WP_Query can be used to order by many things, including custom fields. But unfortunately, ordering by taxonomy terms or categories is not directly possible.

This tutorial explores two different workarounds to accomplish this. Both are quite complicated to set up, so if possible, we recommend choosing a built-in WP_Query order instead. But if you absolutely need this type of ordering, read on.

Two approaches

Below, we describe two different approaches to ordering a query by terms or categories in a custom WP_Query template, then (optionally) output a heading above each group of posts in a term/category, and wrap each group in a container element (for styling).

Which approach to choose depends on your situation:

Both approaches also work if posts have multiple terms. In that case, the posts are ordered by their first term, where the order of post terms to determine this is the same as the order set for the main term order for the query. Make sure to read and apply the setup steps for the chosen approach, as there is a part in the code that depends on whether posts have multiple terms or not (see step 5 below in each described setup).

Approach A – no pagination, sorting within term groups

A – Overview

This setup can only be used if you don’t need pagination. It will not work with a Pager facet or other pagination. The posts_per_page parameter must be set to -1 to retrieve all posts at once. The upside is that this setup does work with a Sort facet, with which you can change the order of posts within the term groups (but not the term groups themselves).

In this approach, we use the normal WordPress loop to go through all posts and store their (first) term and post data in an array $posts_by_term.

Then we reset the loop, and query all terms of the taxonomy in the desired order with get_terms(). In a new loop, we then go through these ordered terms, and for each we (optionally) output a term heading, and loop through and display all posts that exist for that term. Each term group is (optionally) wrapped in a container <div> with a term class for easy styling.

A – Setup

To get this working properly, follow these steps:

  1. Set the taxonomy to order by in line 9
  2. Set the desired term order in lines 12-13.
    If you want to use term_order for a custom manual order, you need a term ordering plugin. FacetWP is compatible with these four, of which we recommend the first two because they have the least known issues (as described on their pages):

    If you are not using term_order in line 12, make sure these plugins are deactivated or not running their custom order automatically on queries on your page, otherwise this setup will fail to order the posts correctly.

  3. Set the desired order of posts within each term group in lines 17-18.
  4. Set the desired post type(s) to retrieve in line 22. Don’t change posts_per_page in line 23 as this approach does not work with pagination (use approach B if you need that).
  5. In lines 46-72, make a choice between using get_the_terms() (option 1) and wp_get_post_terms() (option 2). Using get_the_terms() is better for performance because it is cached, while wp_get_post_terms() is not. But you can only use get_the_terms() if your desired term order is set to name and ASC in lines 12-13. Or if all of your posts only have one term selected each. If any posts have multiple terms, or if you need another term order than name and ASC, like term_order, you need to use wp_get_post_terms() (option 2). Make sure to comment out or remove the option you are not using.
  6. If you don’t want a heading above each term group of posts, remove line 114.
  7. Customize the post output after the post heading in line 127.
  8. For debugging purposes, in lines 129-152 the code outputs the terms for each post in the correct order, and the value for term_order (if that is in use). Remove that part when done or not needed.

[order_by_term_option_a]

Approach B – pagination, but no sorting

B – Overview

Choose this setup if you need pagination and you want to use a Pager facet. The posts_per_page parameter can be set however you like. The downside is that this setup does not work with a Sort facet.

In this approach, we use a normal WordPress loop to go through all posts. To output the posts in the desired term order, we use a post_clauses filter.

For each post in the loop, we get its (first) term(s). We compare this with the current term (that we track using a tracking variable), to output an optional term heading and an (optional) wrapper <div> around each term group, with a term class for easy styling.

B – Setup

To get this working properly, follow these steps:

  1. Set the taxonomy to order by in line 8
  2. Set the desired term order in lines 11-12.
    If you want to use term_order for a custom manual order, you need a term ordering plugin. FacetWP is compatible with these four, of which we recommend the first two because they have the least known issues (as described on their pages):

    If you are not using term_order in line 11, make sure these plugins are deactivated or not running their custom order automatically on queries on your page, otherwise this setup will fail to order the posts correctly.

  3. Set the desired order of posts within each term group in lines 16-17.
  4. Set the desired post type(s) to retrieve in line 21. Set posts_per_page in line 22 as desired.
  5. In lines 101-127, make a choice between using get_the_terms() (option 1) and wp_get_post_terms() (option 2). Using get_the_terms() is better for performance because it is cached, while wp_get_post_terms() is not. But you can only use get_the_terms() if your desired term order is set to name and ASC in lines 11-12. Or if all of your posts only have one term selected each. If any posts have multiple terms, or if you need another term order than name and ASC, like term_order, you need to use wp_get_post_terms() (option 2). Make sure to comment out or remove the option you are not using.
  6. If you don’t want a heading above each term group of posts, remove line 145.
  7. Customize the post output after the post heading in line 158.
  8. For debugging purposes, in lines 160-183 the code outputs the terms for each post in the correct order, and the value for term_order (if that is in use). Remove that part when done or not needed.

[order_by_term_option_b]