WordPress WP_Query Performance Example: Build Fast Archive Queries
Improve WP_Query performance with IDs-only queries, no_found_rows, cache priming choices, taxonomy filters, meta-query caution, and transient caching.
Published
April 21, 2026
Reading Time
3 min read
Updated
April 21, 2026

Implementation Notes
Extension points, code paths, and implementation choices that should survive contact with production.
Best For
WordPress developers, agencies, and technical teams building custom plugin or theme functionality with cleaner operational defaults.
Primary Topics
Editorial Focus
Build Pattern: Extension points, code paths, and implementation choices that should survive contact with production. Updated on April 21, 2026.
Full Report
Last reviewed: April 21, 2026
WP_Query is one of the most powerful APIs in WordPress, but it is also easy to make archive pages slow. The problem is rarely one query alone. It is usually too many returned fields, unnecessary pagination counts, expensive meta queries, sticky post behavior, and cache misses stacking together.
This article shows practical WP_Query performance patterns for plugin and theme code. It focuses on query shape, not server tuning.
Use case
These patterns fit custom archive sections, related posts blocks, admin reports, dashboards, and plugin widgets that need a controlled list of posts. They do not replace proper indexing, object caching, or database review for very large sites.
Fast list of post IDs
If the code only needs IDs, do not request full post objects.
$query = new WP_Query(
array(
'post_type' => 'post',
'post_status' => 'publish',
'posts_per_page' => 10,
'fields' => 'ids',
'no_found_rows' => true,
'ignore_sticky_posts' => true,
'update_post_meta_cache' => false,
'update_post_term_cache' => false,
)
);
$post_ids = $query->posts;
fields => ids reduces the amount of data returned. no_found_rows skips total count calculation when you do not need pagination. Disabling meta and term cache priming can help when the code will not use those values.
Prime only what you use
If the template will immediately call get_the_terms() or read post meta for every result, disabling cache priming can make performance worse. The correct choice depends on the next step. Optimize the full request path, not one query argument in isolation.
For an IDs-only lookup that feeds another system, disable unnecessary priming. For a rendered card grid with categories and metadata, let WordPress prime the data it will need.
When not to use no_found_rows
Do not set no_found_rows when the UI needs accurate pagination. WordPress needs the total row count to calculate the number of pages. Use it for short lists, related content, widgets, and internal lookups where total count is irrelevant.
Taxonomy query example
$security_posts = new WP_Query(
array(
'post_type' => 'post',
'post_status' => 'publish',
'posts_per_page' => 6,
'ignore_sticky_posts' => true,
'no_found_rows' => true,
'tax_query' => array(
array(
'taxonomy' => 'category',
'field' => 'slug',
'terms' => array( 'hardening-notes' ),
),
),
)
);
Taxonomy queries are usually more predictable than broad meta queries. If a value is used for filtering and archives, consider whether it belongs in a taxonomy instead of post meta.
Be careful with meta queries
Meta queries are flexible, but they can be expensive on large sites. Avoid using post meta as a general-purpose reporting database. If the feature needs frequent filtering, sorting, and aggregation at scale, consider a custom table or a different data model.
$query = new WP_Query(
array(
'post_type' => 'post',
'posts_per_page' => 10,
'meta_query' => array(
array(
'key' => 'vulnwp_score',
'value' => 80,
'type' => 'NUMERIC',
'compare' => '>=',
),
),
)
);
Cache expensive custom lists
For repeated non-personalized lists, cache the final list of IDs for a short interval and delete the cache when relevant content changes. This avoids repeating the same query on every request.
$cache_key = 'vulnwp_featured_ids_v1';
$post_ids = get_transient( $cache_key );
if ( false === $post_ids ) {
$query = new WP_Query(
array(
'post_type' => 'post',
'posts_per_page' => 6,
'fields' => 'ids',
'no_found_rows' => true,
)
);
$post_ids = $query->posts;
set_transient( $cache_key, $post_ids, 10 * MINUTE_IN_SECONDS );
}
Production checklist
- Request only the fields the feature needs.
- Use
no_found_rowsfor non-paginated lists. - Disable meta and term cache priming only when those values are not used.
- Prefer taxonomy filters for editorial grouping.
- Avoid unbounded queries.
- Measure real query behavior on production-like data.
Common mistakes
- Fetching all posts then slicing in PHP. Let the database limit the result set.
- Using meta queries for everything. They do not replace a deliberate data model.
- Skipping pagination logic. Archive pages need accurate counts.
- Ignoring sticky posts. Sticky behavior can surprise custom lists.
- No performance testing. A query that works on 50 posts may fail on 50,000.
Related reading
For custom tables at larger scale, read the dbDelta custom table example. For taxonomy-based filtering, read the custom taxonomy example.


