Tax_Query of WP_Query

In this guide, I would like to talk in-depth about using the tax_query parameter when creating custom loops with WP_Query, query_posts, get_posts or even when filtering existing loops with the pre_get_posts filter hook.

This one will be super-similar to a guide about Meta_Query, but, I guess, easier.

Getting Posts by Custom Taxonomy

Let’s start with the examples how you can use Tax_Query when creating custom loops with the WP_Query class.

1. Get posts by a custom taxonomy using its slug or ID

It is a pretty simple example. Let’s say, we want to get all coffee shops (a custom post type coffeeshop) in Athens (a custom taxonomy city), we can do it this way:

$q = new WP_Query( array(
	'post_type' => 'coffeeshop',
	'tax_query' => array(
		// we have only one taxonomy condition in this tax_query
		array(
			'taxonomy' => 'city',
			'field' => 'slug', // defaults to term_id
			'terms' => array( 'athens' ),
		),
	),
) );

We can easily query posts by a custom taxonomy ID instead of a slug, by changing these lines:

		array(
			'taxonomy' => 'city',
			'terms' => array( 5 ),
		),

The field parameter default value is term_id, so we can feel free to remove it completely.

2. Get posts by multiple custom taxonomies

Before diving into the examples, I’d like to highlight that it can have two meanings:

  • Either we get posts by multiple taxonomy terms of a single taxonomy,
  • or we get posts by multiple custom taxonomies themselves.

Let’s take a look at both situations.

First of all, getting posts by multiple taxonomy terms is super simple – all we need to do is provide multiple values of the terms parameter. Something like this:

$q = new WP_Query( array(
	'post_type' => 'coffeeshop',
	'tax_query' => array(
		array(
			'taxonomy' => 'city',
			'field' => 'slug',
			'terms' => array( 'athens', 'lisbon', 'copenhagen' ),
		),
	),
) );

The code above will return the posts that have at least one of the mentioned terms, if you need the posts to have all of them, read below about the operator parameter.

If we need to query multiple taxonomies, we need to add more arrays to tax_query:

$q = new WP_Query( array(
	'post_type' => 'coffeeshop',
	'tax_query' => array(
		'relation' => 'AND',
		// custom taxonomy condition 1
		array(
			'taxonomy' => 'city',
			'field' => 'slug',
			'terms' => array( 'athens', 'lisbon', 'copenhagen' ),
		),
		// custom taxonomy condition 2
		array(
			'taxonomy' => 'laptopfriendly',
			'field' => 'slug',
			'terms' => array( 'yes' ),
		),
	),
) );

A couple of notes to the code below:

  • As you can see, the relation parameter comes into play. It allows us to define the relation between the taxonomy arrays, by default, its value is AND, it means that both conditions should apply, but you can change it to OR, if you need to.
  • Yes, I know, that the second condition can be better achieved with a custom field 😁

3. “operator” parameter of Tax_Query 

Each taxonomy array of tax_query can have the operator parameter with one of the following values:

  • IN (default) – at least one of the terms,
  • NOT IN – none of the terms,
  • AND – all of the terms,
  • EXISTS – any term from a given taxonomy,
  • NOT EXISTS – none of the terms of a given taxonomy.

Let’s come back to our coffee shop example and try to get the ones that have any taxonomy term city assigned:

$q = new WP_Query( array(
	'post_type' => 'coffeeshop',
	'tax_query' => array(
		array(
			'taxonomy' => 'city',
			'operator' => 'EXISTS',
		),
	),
) );

4. Complex conditions

The cool thing about arrays inside the tax_query parameter is that you combine them together with the relation parameter in any order and on different levels.

$args = array(
	'tax_query' => array(
		'relation' => 'AND',
		array(
			'taxonomy' => 'taxonomy_1',
			'field' => 'slug',
			'terms' => array( 'taxonomy-1-term-1' ),
		),
		array(
			'relation' => 'OR',
			array(
				'taxonomy' => 'taxonomy_2',
				'operator' => 'EXISTS',
			),
			array(
				'taxonomy' => 'taxonomy_3',
				'operator' => 'EXISTS',
			),
		),
	),
);
$q = new WP_Query( $args );

With the help of this code snippet you can get posts that have the “Term 1” term of a “Taxonomy 1” AND any terms from either “Taxonomy 2” OR “Taxonomy 3”.

Modifying Tax_Query of an existing loop with pre_get_posts

I think it is also worth showing you examples of modifying the tax_query argument of an existing loop using the pre_get_posts action hook (or the query_loop_block_query_vars filter hook).

By the way, the same approach I am about to show is used by my Taxonomy Filter Block plugin.

Let’s say that we have a specific loop which may or may not have the tax_query parameter at this moment, and we need to add another condition into that. How we can do it?

add_action( 'pre_get_posts', function( $query ) {
	
	// the result tax_query
	$tax_query = array();
	
	// let's get the current tax_query value from the loop
	$current_tax_query = $query->get( 'tax_query' );
	
	// that's what we need to add
	$new_tax_query = array(
		'taxonomy' => 'city',
		'field' => 'slug',
		'terms' => array( 'athens' ),
	);
	
	// if tax_query already exists, we need to merge them
	if( ! empty( $current_tax_query ) ) {
		$tax_query[ 'relation' ] = 'AND';
		$tax_query[] = $current_tax_query;
	}
	
	$tax_query[] = $new_tax_query;

	$query->set( 'tax_query', $tax_query );
	
} );

This code can be written in different ways, I tried to make it as simple as possible for you.

Category and Tag Parameters of WP_Query

All the parameters in this chapter have been kind of obsolete since the moment the tax_query parameter appeared. However, you can find them pretty often when working with the code of other WordPress developers.

Let’s take a look at all of them.

Category parameters:

ParameterDescription
catPass a category ID in this parameter. You can pass multiple values, comma-separated.
category_nameUse a category slug (not name) here, multiple comma-separated values are also supported.
category__andAn array of category IDs a post should have.
category__inAn array of category IDs a post must have at least one of them.
category__not_inAn array of category IDs a post shouldn’t have.

For example, when we need to display posts from either “WordPress” or “WooCommerce” category:

$q = new WP_Query( array( 'category_name' => 'wordpress,woocommerce' ) );

Or when we need to display posts that have posts of them at the same time:

$q = new WP_Query( array( 'category_name' => 'wordpress+woocommerce' ) );

Tag parameters:

ParameterDescription
tagAccepts a tag slug, multiple comma-separated values are supported.
tag_idAccepts a tag ID, multiple comma-separated values are supported.
tag__andAn array of tag IDs, a post should have all of them.
tag__inAn array of tag IDs, a post should have at least one of them.
tag__not_inAn array of tag IDs a post shouldn’t have.
tag_slug__andAn array of tag slugs, a post should have all of them.
tag_slug__inAn array of tag slugs, a post should have at least one of them.

Taxonomy parameters:

ParameterDescription
{$taxonomy_name}This parameter accepts taxonomy term slugs.

For example:

$q = new WP_Query( array( 'city' => 'athens' ) );
Misha Rudrastyh

Misha Rudrastyh

Hey guys and welcome to my website. For more than 10 years I've been doing my best to share with you some superb WordPress guides and tips for free.

Need some developer help? Contact me

Follow me on X