Tips・WordPress templates and fetching posts

Filtering by category and taxonomy with tax_query

How to write tax_query to list only posts with a particular custom taxonomy term. Covers what field, operator and relation mean, AND/OR across multiple conditions, and why filtering sometimes doesn't work, with code.

“I only want winter products in this list.”—tax_query is how you filter by custom taxonomy terms. Learning this basic shape is enough to get going.

<?php
$query = new WP_Query([
  'post_type' => 'shohin',
  'tax_query' => [
    [
      'taxonomy' => 'season',   // the taxonomy name
      'field'    => 'slug',     // what you're passing to terms
      'terms'    => 'winter',   // the term you want
    ],
  ],
]);
?>

tax_query has a nested structure—an array of condition arrays. That’s the first hurdle, so getting your eye used to the shape keeps you out of trouble.

Theme developmentCreating "products" with a custom post typeWant to start from how custom post types and taxonomies are created?

What the three keys mean

KeyRoleValues you can use
taxonomyWhich taxonomyThe slug you registered with register_taxonomy() (category / post_tag work too)
fieldWhat you’re passing to terms'slug' / 'term_id' (default) / 'name'
termsThe term(s) you wantA single value or an array
operatorThe direction of the condition'IN' (default) / 'NOT IN' / 'AND' / 'EXISTS' / 'NOT EXISTS'

Choosing an operator

// winter or spring (has either one)
[
  'taxonomy' => 'season',
  'field'    => 'slug',
  'terms'    => ['winter', 'spring'],
  'operator' => 'IN',        // the default
],

// anything but winter
[
  'taxonomy' => 'season',
  'field'    => 'slug',
  'terms'    => 'winter',
  'operator' => 'NOT IN',
],

// only items that have both winter and limited
[
  'taxonomy' => 'season',
  'field'    => 'slug',
  'terms'    => ['winter', 'limited'],
  'operator' => 'AND',
],

// everything that has any season at all
[
  'taxonomy' => 'season',
  'operator' => 'EXISTS',
],

The difference between IN and AND when terms is an array comes up a lot in practice. “Any one of these” is IN; “all of these” is AND.

Combining several taxonomies

Line up two or more conditions and let relation decide how they’re joined.

<?php
$query = new WP_Query([
  'post_type' => 'shohin',
  'tax_query' => [
    'relation' => 'AND',   // only items satisfying both
    [
      'taxonomy' => 'season',
      'field'    => 'slug',
      'terms'    => 'winter',
    ],
    [
      'taxonomy' => 'brand',
      'field'    => 'slug',
      'terms'    => ['shimaenaga', 'kotori'],
    ],
  ],
]);
?>
  • 'relation' => 'AND'—satisfy every condition (this is also the default)
  • 'relation' => 'OR'—satisfy any one of them

For categories and tags, the dedicated arguments are shorter

Standard categories and tags have shorter forms available. For simple filtering, these are plenty.

'category_name' => 'news',          // category slug
'category__in'  => [3, 7],          // category IDs
'tag'           => 'sale',          // tag slug
'tag__in'       => [12],            // tag IDs

You need tax_query when you’re using a custom taxonomy, or when you need finer conditions like NOT IN and AND, or combinations of taxonomies.

Example: filter by the term you’re currently viewing

For when you want conditional output of a term’s posts on its taxonomy archive page.

<?php
$term = get_queried_object();   // the term currently being displayed
$query = new WP_Query([
  'post_type' => 'shohin',
  'tax_query' => [
    [
      'taxonomy' => $term->taxonomy,
      'field'    => 'term_id',
      'terms'    => $term->term_id,
    ],
  ],
  'posts_per_page' => 12,
]);
?>

get_queried_object() returns “the subject of the current page,” which on a taxonomy archive is a term object.

<?php
$terms = wp_get_post_terms(get_the_ID(), 'season', ['fields' => 'ids']);
$related = new WP_Query([
  'post_type'      => 'shohin',
  'post__not_in'   => [get_the_ID()],
  'posts_per_page' => 4,
  'tax_query'      => [
    [
      'taxonomy' => 'season',
      'field'    => 'term_id',
      'terms'    => $terms,
    ],
  ],
]);
?>

Pass ['fields' => 'ids'] to wp_get_post_terms() and you get an array of IDs, which drops straight into terms.

TipsBuilding related posts without a pluginMore related-post patterns

Combining with custom field conditions

tax_query and meta_query can be used together—filtering like “winter products that are in stock.”

'tax_query'  => [['taxonomy' => 'season', 'field' => 'slug', 'terms' => 'winter']],
'meta_query' => [['key' => 'stock', 'value' => 0, 'compare' => '>', 'type' => 'NUMERIC']],
TipsFiltering and sorting by custom field with meta_queryHow to write meta_query, and how to sort with it

Summary

  1. tax_query has a nested structure—condition arrays inside an array. The three-part set is taxonomy / field / terms
  2. Passing a slug means 'field' => 'slug' is required (the default is term_id)
  3. Switch between IN / NOT IN / AND with operator, and join multiple conditions with relation
  4. For standard categories and tags, category_name and tag are shorter. When nothing works, check the names with var_dump

Once filtering is second nature, you can build many different lists from the same post data.

FAQ

What should I put in tax_query's field?
It depends on what you pass to terms. Pass a slug ('winter' and so on) and it's 'slug'; pass an ID (a number) and it's 'term_id'; pass a name and it's 'name'. The default is term_id, so specifying field is required when you pass a slug.
How do I filter by more than one taxonomy?
Put two or more conditions in the tax_query array and specify 'relation' => 'AND' (satisfy both) or 'OR' (either one). Omitting relation gives you AND.
Can I use tax_query for categories and tags too?
Yes. Set taxonomy to 'category' or 'post_tag'. For simple filtering, though, the dedicated category_name and tag arguments are shorter.
My tax_query does nothing and everything shows up.
Check that the taxonomy name (the slug you registered with register_taxonomy) and the term slug are correct. Write a name that doesn't exist and the condition is ignored, showing everything. Confirm the real names with get_taxonomies() or get_terms.