WP_Query vs query_posts() vs get_posts()

Most WordPress developers have used WP_Query, query_posts and get_posts at one point or the other. Question is – how do you know the use case for each?

In this article i will try to clarify.

First let us understand the concept of main query and secondary queries in WordPress.

When someones visits a link of your wordpress site, wordpress runs a query. This query takes into account any request parameters or query vars present in the url. This is the main query.

Interestingly, the main query may not be the first query! Because WordPress has lot of stuff to take care of before the main query is run – setting up theme, plugins, running action hooks and filters. So there may be queries to handle all that stuff . These are secondary queries and some of them might run before the main query.

From WordPress docs –

  • The main query/ loop is based on the URL request and is initialised before theme templates are processed
  • Secondary queries/ loops are queries (using new WP_Query) in theme template or plugin files

query_posts is a global method that can be used to alter the main query. But this method also affects the global $wp_query object which contains a boatload of information needed to run your site correctly. So using query_posts can break your plugins and custom code block and modules – which use the information stored in $wp_query. If you must use query_posts at ll then call wp_reset_query right after you do your custom stuff with the query/loop, though your site may not work properly even after that!

// Your custom query arguments
$args = array(
  'posts_per_page' => 10
)

// This changes the main query
query_posts( $args );
 
// The new Default Loop
while ( have_posts() ) : the_post();
   // do your stuff here
endwhile;

// Restore the main query - if you do not use this, subsequent functions/modules/plugin may break
wp_reset_query();

While you can use query_posts with wp_reset_query as a safeguard, a better option is to use an action hook – pre_get_posts. So the example above can be modified to –

function wdv_custom_number_of_posts( $query ) {
    if ( $query->is_main_query() ) {
        $query->set( 'posts_per_page', 10 );
    }
}
add_action( 'pre_get_posts', 'wdv_custom_number_of_posts');

Note that pre_get_posts is an action hook and does not alter global variables. Also pre_get_posts affects all queries, not only the main query, hence if you are looking to modify only the main query then you need to set a conditional statement as shown above.

Most custom themes these days have home/other pages that need to use one or multiple custom queries based on the theme requirements. As such there is hardly any need to modify the main query and you can safely avoid using query_posts.

The two main options to create custom queries (secondary queries) different from the main query are – new instance of WP_Query class and get_posts global method.

Which one to use – depends on the developer’s preference.

get_posts is simpler. Just use an array of arguments and you are set to go!

$args = array('posts_per_page' => 10);

$posts = get_posts( $args );

This returns an array of post objects which can be iterated with a simple foreach loop

foreach( $posts as $p ) {
   echo $p->post_title;
}

While using WP_Query, it becomes a bit more complicated.

$query = new WP_Query( $args );

while( $query->have_posts() ):
	if ( $query->have_posts() ); $query->the_post();
	echo the_post_title();
endwhile;

wp_reset_postdata();

You will also need to use wp_reset_postdata() as shown above. This is because use WP_Query affects the global $post variable and you would want to revert to its original value after the loop is run.

Conclusion: Use WP_Query and get_posts for custom query/loop. Avoid query_posts. If you must modify the main query then use action hook pre_get_posts.