Every time you pass a tax_query argument to WP_Query, WordPress builds a WP_Tax_Query object internally. Understanding how it works unlocks complex filtering across multiple taxonomies — tags, categories, custom taxonomies, and relationships between them.
Problem: How do you query posts that match multiple taxonomy conditions at once — for example, posts in a specific category AND tagged with a particular term, or posts belonging to either of two categories?
Solution: Pass a tax_query array to WP_Query with relation set to AND or OR, and define one sub-array per taxonomy condition. Use the field key to match by term_id, slug, or name.
Basic single-taxonomy query:
// Posts in the "tutorials" category AND tagged "wordpress"
$query = new WP_Query( [
'post_type' => 'post',
'tax_query' => [
'relation' => 'AND',
[
'taxonomy' => 'category',
'field' => 'slug',
'terms' => 'tutorials',
],
[
'taxonomy' => 'post_tag',
'field' => 'slug',
'terms' => 'wordpress',
],
],
] );
Nested tax_query for complex OR/AND logic:
// (category = 'news' OR category = 'announcements') AND tag != 'archived'
$query = new WP_Query( [
'post_type' => 'post',
'tax_query' => [
'relation' => 'AND',
[
'relation' => 'OR',
[
'taxonomy' => 'category',
'field' => 'slug',
'terms' => 'news',
],
[
'taxonomy' => 'category',
'field' => 'slug',
'terms' => 'announcements',
],
],
[
'taxonomy' => 'post_tag',
'field' => 'slug',
'terms' => 'archived',
'operator' => 'NOT IN',
],
],
] );
Using term IDs and the EXISTS operator:
// Posts that have ANY value for the custom taxonomy 'color'
$query = new WP_Query( [
'post_type' => 'product',
'tax_query' => [
[
'taxonomy' => 'color',
'operator' => 'EXISTS',
],
],
] );
// Posts with NO term in the 'color' taxonomy
$uncategorised = new WP_Query( [
'post_type' => 'product',
'tax_query' => [
[
'taxonomy' => 'color',
'operator' => 'NOT EXISTS',
],
],
] );
NOTE: When querying by term slug or name, WordPress runs an additional query to convert the slug/name to a term ID. For high-traffic queries, use 'field' => 'term_id' with pre-resolved IDs to save a database round-trip per query execution.