WP_Query
What WP_Query is
WP_Query is WordPress’s main class for querying posts from the database. It powers:
- The main loop
- Custom loops (recommended way)
- Complex queries (meta, tax, date, pagination, etc.)
Use it instead of query_posts() (which you should basically never use).
Basic Usage Pattern
$args = [
'post_type' => 'post',
'posts_per_page' => 10,
];
$query = new WP_Query($args);
if ($query->have_posts()) {
while ($query->have_posts()) {
$query->the_post();
the_title();
}
wp_reset_postdata();
}
Key points
- Always call
wp_reset_postdata()after a custom loop $query->the_post()sets up global$post
Common Query Arguments
Post Type
'post_type' => 'post' // or 'page', 'product', 'custom_post_type'
Multiple post types:
'post_type' => ['post', 'page']
Pagination
$paged = get_query_var('paged') ? get_query_var('paged') : 1;
$args = [
'post_type' => 'post',
'posts_per_page' => 5,
'paged' => $paged,
];
Pagination links:
echo paginate_links([
'total' => $query->max_num_pages,
]);
Ordering Results
'orderby' => 'date', // date | title | meta_value | rand
'order' => 'DESC', // ASC | DESC
Order by meta value:
'meta_key' => 'price',
'orderby' => 'meta_value_num',
'order' => 'ASC',
Meta Queries (Custom Fields)
Single meta condition:
'meta_query' => [
[
'key' => 'featured',
'value' => '1',
'compare' => '=',
]
]
Multiple conditions:
'meta_query' => [
'relation' => 'AND',
[
'key' => 'price',
'value' => 100,
'compare' => '>=',
'type' => 'NUMERIC',
],
[
'key' => 'in_stock',
'value' => 'yes',
],
]
Taxonomy Queries
Basic taxonomy query:
'tax_query' => [
[
'taxonomy' => 'category',
'field' => 'slug',
'terms' => 'news',
],
]
Multiple taxonomies:
'tax_query' => [
'relation' => 'AND',
[
'taxonomy' => 'category',
'field' => 'term_id',
'terms' => [1, 2],
],
[
'taxonomy' => 'post_tag',
'field' => 'slug',
'terms' => ['featured'],
],
]
Excluding / Including Posts
Exclude by ID:
'post__not_in' => [12, 34]
Include only specific posts:
'post__in' => [10, 20, 30],
'orderby' => 'post__in',
Date Queries
'date_query' => [
[
'after' => '2024-01-01',
'before' => '2024-12-31',
'inclusive' => true,
],
]
Relative dates:
'date_query' => [
[
'after' => '1 week ago',
],
]
Checking Results Without Loop
if ($query->found_posts > 0) {
echo $query->found_posts;
}
Access posts array directly:
$posts = $query->posts;
Performance Tips
Limit fields:
'fields' => 'ids'
Disable unnecessary counts:
'no_found_rows' => true
Don’t prime meta/term cache:
'update_post_meta_cache' => false,
'update_post_term_cache' => false,
Main Query vs Custom Query
Modify main query (archive, search, etc.):
add_action('pre_get_posts', function ($query) {
if (!is_admin() && $query->is_main_query() && is_post_type_archive('product')) {
$query->set('posts_per_page', 12);
}
});
Never use WP_Query inside pre_get_posts.
Quick Debugging
echo '<pre>';
print_r($query->query_vars);
echo '</pre>';
Or log SQL:
add_filter('posts_request', function ($sql) {
error_log($sql);
return $sql;
});