WordPress is an extremely popular CMS and blogging platform with powerful, thoughtful features. For instance, it automatically queries data based on the page you’re visiting — category pages show the latest posts in that category, while post pages display the article content. This is certainly convenient, but it comes at the cost of flexibility.
So here’s the question: how do you perform custom queries in WordPress?
Before discussing custom queries, let’s understand WordPress’s Main Query. WordPress’s mechanism works like this: when it detects a visit to a specific page, it creates a global variable ($query), passes the page’s information as parameters into $query, then executes $query’s query method and stores the results. When you use have_posts(), the_post(), and similar functions in template files, they all access data from $query.
There are roughly 3 methods for custom queries in WordPress:
1. pre_get_posts Action
The pre_get_posts Action fires after the $query variable is created but before the query executes, allowing you to modify $query’s parameters to change the query results. This Action passes a reference to the $query object, so any modifications to it take effect immediately. Since it modifies the Main Query, you can use it to alter the main query’s content.
For example, to exclude certain categories from the homepage post list:
function exclude_category( $query ) {
if ( $query->is_home() && $query->is_main_query() ) {
$query->set( 'cat', '-1,-2' );
}
}
add_action( 'pre_get_posts', 'exclude_category' );
Or to set the number of posts per page:
function home_pagesize( $query ) {
if ( is_admin() )
return;
if ( is_home() ) {
$query-->set( 'posts_per_page', 5 );
return;
}
}
add_action( 'pre_get_posts', 'home_pagesize', 1 );
Note that in the example above, we use is_admin() to exclude admin pages, because this Action also affects backend queries.
2. query_posts Function
The query_posts function is another way to modify the Main Query, but more directly — it overwrites the $query variable entirely. WordPress officially recommends against using this function, suggesting pre_get_posts instead. Both achieve similar functionality, but query_posts increases page load time (requiring a re-query), introduces code confusion, and has other drawbacks. So let’s not make trouble for ourselves.
3. WP_Query Class
The Main Query we discussed earlier uses the WP_Query class. This class enables fully customized queries, encapsulating methods to handle post query requests.
For example, say you want to display related posts at the bottom of an article — 6 random posts from the same category. You’d write something like:
// The Main Loop
$post_id = get_the_ID();
$post_category = get_the_category($post_id);
$cat_id = $post_category->term_id;
$args = array(
'post_status' => 'publish',
'orderby' => 'rand',
'cat' => $cat_id,
'post__not_in' => array($post_id),
'paged' => 1,
'posts_per_page' => 6
);
$my_query = new WP_Query($args);
if ( $my_query->have_posts() ) {
$list = '<ul>';
while ( $my_query->have_posts() ) {
$my_query->the_post();
$list .= sprintf('<li><a href="%s">%s</a></li>', the_permalink(), the_title());
}
$list .= '</ul>';
echo $list;
}
wp_reset_postdata();
// The Main Loop
Here we first get the post ID, retrieve the category from the ID, construct the parameters, create a WP_Query object, then use have_posts() to iterate through the results.
WP_Query accepts an enormous number of constructor parameters — far too many to list here. Through various combinations of these parameters, we can achieve virtually any custom query.
In summary: to modify the Main Query (e.g., changing sort order on category pages, posts per page, excluding certain posts), use the pre_get_posts Action. For fully custom queries (e.g., latest posts in a sidebar or a custom page), use the WP_Query class. query_posts should generally be avoided.