Worpress queries: When to use WP_Query, get_posts, query_posts or pre_get_posts

When you’re creating a custom theme, at some point you may to want to bring back a set of results other than what Wordpress defaults to. The question is, do you use WP_Query, get_posts, query_posts or pre_get_posts?

When you’re creating a custom theme, at some point you may to want to bring back a set of results other than what WordPress defaults to. The question is, do you use WP_Query, get_postsquery_posts or pre_get_posts?

Scenario 1 – You just need an archive page
Soluion: None of the above

If something can be accomplished without writing code, let WordPress do it for you.

  • If you want to move your main blog posts archive (ie. list of all your posts, usually just the excerpts) from the front page to another page (eg. a page entitled ‘Blog’), all you need to do is:
    a) create a new page called ‘Blog’
    b) Go to Settings > Reading and select ‘Front page displays: A static page’ and select your posts page: Select Blog page
  • If you want an archive or single page for a custom post type, use the relevant templates from the template hierarchy. Eg. For a custom post type ‘books’, use archive-books.php.  The default loop will display all ‘books’ posts, and each individual post will be displayed with single-books.php.  The exact link will depend on your permalink structure. If you’re using the ‘post name’ option (see Settings > Permalinks) then http://yourdomain/books will automatically load your books archive template.

Scenario 2 – Your page already utilises a loop and you need an additional loop
Solutions: WP_Query or get_posts()

The difference between the two is that WP_Query returns an object and get_posts returns an array.  If the content you want is fairly lightweight, for example a list of titles, then you can use get_posts and loop through the array with a foreach.  If you want a fully blown WordPress Loop and the ability to use template tags such as the_content(), go for the WP_Query option.

Scenario 3 – you want to alter the main loop
Solutions: query_posts() or pre_get_posts

By the time WordPress loads your template and runs the code in your template, it has already run a query on the database and is holding the results ready for you to use in the loop.  If you use query_posts to modify the results, WordPress throws away the previous results and runs another query on the database to bring back your results.
Because of this, it is generally recommended to hook into pre_get_posts instead. To do this you would create a function in your theme’s functions.php.  This enables you to alter the query before it is run. However, this affects all of the queries on your site, front-end and admin, if you’re not careful.  Therefore you need to put in checks to make sure it’s not an admin page/it is the main loop/it is in fact the template you wanted to affect.
In this example I have created a custom post type called ‘product’, and have a custom field called ‘colour’. My WordPress front page is set to ‘Your latest posts’. WordPress will use the home.php template to display this if it is available. Here I am altering the query for the home page to only show Products that are red:

function bb_red_things( $query ) {
	if ( is_home() ) {
        // Set post type to 'product'
        $query->set( 'post_type', 'product' );
        // Limit to products with a custom field 'colour' set to 'red'
		$query->set( 'meta_key', 'colour');
		$query->set( 'meta_value', 'red');
add_action( 'pre_get_posts', 'bb_red_things' );

Note that pre_get_posts doesn’t work with Pages. Also, some template tags will not work (as they’ve not been set up yet at this point). For example, is_front_page() will not work, although is_home() will work. See the Codex on pre_get_posts for more details and examples.