Skip to content

10 WordPress query_posts tips you probably don’t know

I have written a really brief query_post tutorial before, and it did quite well, but both WordPress and my own skills, have advanced considerably since then. So I thought it would be interesting to revisit the query_posts command and see what has changed.

The query_posts command is one of the most useful in all of WordPress. It is this command that allows all the content to be queried (extracted from the database) and then displayed by the loop. It’s powerful because it gives an incredibly flexible method for retrieving and filtering the posts and pages in pretty much any way you can imagine.

At its most basic you can use query_posts to grab just the single latest post, or to get the latest 100 posts. At its most complex you could select a random collection of the posts that a specific blog author has posted in a collection of site categories with a specific tag – although why you’d want to do that is a mystery to me :)

The query_posts page on the codex is a great resource for picking up new ways of doing things but there are probably areas you miss (or skip) when reading it, and below are some of the more interesting features you probably don’t know.

1. Everything changes

The first thing to note is that the best way to use the query_posts command… is not to use it. On the query posts page itself there is this comment:

The query_posts function is intended to be used to modify the main page Loop only. It is not intended as a means to create secondary Loops on the page. If you want to create separate Loops outside of the main one, you should create separate WP_Query objects and use those instead. Use of query_posts on Loops other than the main one can result in your main Loop becoming incorrect and possibly displaying things that you were not expecting.

The proper way is to use the query class itself.

<?php
$query = 'posts_per_page=10';
$queryObject = new WP_Query($query);
// The Loop...
if ($queryObject->have_posts()) {
	while ($queryObject->have_posts()) {
		$queryObject->the_post();
		the_title();
		the_content();
	}
}
?>

2. Different Queries

The ‘traditional’ method for using query posts, and the method I have used throughout this tutorial, is to pass the query posts parameters in query string format (eg variable1=value1&variable2=value2&…), however for the last year or two there has been a move towards using arrays for setting the query parameters. This is a method I have been slowly transitioning to over the last year or so and it’s definitely nicer to use, however for quick queries, you can’t beat a string of text :)

<?php
// GOOD
// select 10 posts from category 1
$query = 'posts_per_page=10&cat=1';
$queryObject = new WP_Query($query);
// The Loop...

// BETTER
$query = array (
    'posts_per_page' => 10,
    'cat' => 1
);
$queryObject = new WP_Query($query);
// The Loop...
?>

Clearly which you use is down to personal preference but I feel that the array option is better for a few reasons:

  1. It is easier to read
  2. You can more easily add conditions to the array using if statements to dynamically create queries
  3. Some of the query parameters (albeit less frequently ued ones such as some of the category and Tag parameters) need to have arrays passed to them.
  4. You can pass a function call as a parameter (as demoed in Tip 6 below)

3. order, order

By default posts are sorted by date, and in most cases that’s all you need, but being able to change the order is also quite handy. For instance you can order posts alphabetically by post title or grab a random handful to display in your sidebar. In WordPress 2.9 the ability to order by comment count was introduced, so that makes popular posts easy to display… assuming you don’t want to use the simplest popular posts code ! :)

<?php
// select posts ordered by comment_count
$query = 'orderby=comment_count';
$queryObject = new WP_Query($query);
// The Loop...
?>

4. Meta Values

Before building WordPress Vote I wasn’t aware these options existed but they ended up being incredibly helpful. On WPVote I store the number of votes using the post meta functions (update_post_meta), so to select the most popular votes I can do:

<?php
// grab posts by post count ordered in descending order (most votes first)
$query = 'meta_key=vote_count&orderby=meta_value&order=DESC';
$queryObject = new WP_Query($query);
// The Loop...
?>

5. Posts? Pages? Attachments?

Posts are what is queried as standard, but you can filter by post type, which currently lets you looks for posts, pages, and attachments – no doubt this will grow in the future. Of the three I think selecting Attachments could be the most interesting and underused. Using this (and the order parameter above) you could easily build some sort of random media display into your site:

<?php
// grab 1 random attachment
$query = 'post_type=attachment&orderby=rand&posts_per_page=1';
$queryObject = new WP_Query($query);
// The Loop...
?>

6. Sticky Posts

Sticky Posts were introduced in WordPress 2.7. They are an easy way to keep specific blog posts at the top of the post list, without messing around with post dates. The standard sticky posts behaviour (across all of the query_posts queries) is to add themselves to the front of the list of returned data. There are two things you may want to do with the Sticky Posts filter.

  1. Default – Select posts with Sticky Posts added first
  2. Select just Sticky Posts (maybe useful in Magazine themes?)
  3. Select posts as normal and ignore Sticky Posts

To select all Sticky Posts:

<?php
$query = array(
    'post__in'=>get_option('sticky_posts')
);
$queryObject = new WP_Query($query);
// The Loop...
?>

To ignore Sticky Posts – they will display in their natural location… just not at the top of the listing:

<?php
$query = array(
    'post__not_in' => get_option('sticky_posts')
);
$queryObject = new WP_Query($query);
// The Loop...
?>

There are extra Sticky Posts examples on the codex.

7. Grab all posts

There are a few different ways to select all the post that have ever been published and this would mostly be used for sitemaps and archive pages. These are great for Google, so I would always recommend having one. The parameters you can use are:

<?php
// showposts is the traditional way of doing it, but it's now deprecated so could be removed at any time
// there are still lots of themes that use showposts (including my own) so it's worth switching over as soon as you can just in case
$query = 'showposts=-1';
$queryObject = new WP_Query($query);
// The Loop...

// the new way to do it
$query = 'posts_per_page=-1';
$queryObject = new WP_Query($query);
// The Loop...

// must admit I have never tried this myself but according to the documentation it will work :)
$query = 'nopaging=true';
$queryObject = new WP_Query($query);
// The Loop...
?>

8. Hooks and Filters

One of the lesser known features of the query_posts functionality is that you can hook into the generated queries. This can actually be quite dangerous, but is also incredibly powerful. WordPress has a number of filters for modifying elements of queries.

Grabbed from the Advanced WordPress Filters section of the Filters reference on the Codex, the filters you can apply to your queries are:

post_limits
applied to the LIMIT clause of the query that returns the post array.
posts_distinct
allows a plugin to add a DISTINCTROW clause to the query that returns the post array.
posts_groupby
applied to the GROUP BY clause of the query that returns the post array (normally empty).
posts_join_paged
applied to the JOIN clause of the query that returns the post array, after the paging is calculated (though paging does not affect the JOIN, so this is actually equivalent to posts_join).
posts_orderby
applied to the ORDER BY clause of the query that returns the post array.
posts_request
applied to the entire SQL query that returns the post array, just prior to running the query.
posts_where_paged
applied to the WHERE clause of the query that returns the post array, after the paging is calculated (though paging does not affect the WHERE, so this is actually equivalent to posts_where).
posts_join
applied to the JOIN clause of the query that returns the post array. This is typically used to add a table to the JOIN, in combination with the posts_where filter.
posts_where
applied to the WHERE clause of the query that returns the post array.

The query_posts parameters are so comprehensive that most people won’t need to touch these, but the reason I discovered these is that I was trying to sort a query by its numeric meta_value however the meta value is a string field type (which means the numbers were ordered – 1, 10, 11, 2, 23, 3 etc.). So I wanted to cast the sort order as an int to be able to sort numerically… and this is what I ended up with:

add_filter ('posts_orderby', 'bm_featureHomeFilterOrder');

function bm_featureHomeFilterOrder ($order = '') {
	global $wpdb;
	$field = $wpdb->postmeta . '.meta_value';
	$order = str_replace($field, 'CAST(' . $field . ' AS UNSIGNED)', $order);
	return $order;
}

9. Rewind Posts

Rewind Posts is a handy little function that lets you run “the loop” and then reset it and run through it again. Calling it is easy.

<?php rewind_posts(); ?>

So to use it you would:

<?php
$query = 'posts_per_page=10';
$queryObject = new WP_Query($query);
// The Loop...

rewind_posts();
// The Loop...
?>

10. Exclude posts from showing (hide duplicate content)

This next (and final) tip is actually something written by Ronalfy on WebLogToolsCollection a couple of years ago – and I now use it in all of my themes (Elemental, and Mimbo Pro both have it built in).

The idea is that the code will stop duplicate posts from being linked when you use multiple WordPress loops on a single page. For example I use it on the homepage of this site so that I can show the latest posts, and then display my categories without worrying about if the latest posts will display again. This means that more different posts will show on the homepage.

My code has evolved from the original code so I will leave the example there for explanation of how it works and post my sample below so you can see what I have done:

$bmIgnorePosts = array();

/**
 * add a post id to the ignore list for future query_posts
 */
function bm_ignorePost ($id) {
	if (!is_page()) {
		global $bmIgnorePosts;
		$bmIgnorePosts[] = $id;
	}
}

/**
 * reset the ignore list
 */
function bm_ignorePostReset () {
	global $bmIgnorePosts;
	$bmIgnorePosts = array();
}

/**
 * remove the posts from query_posts
 */
function bm_postStrip ($where) {
	global $bmIgnorePosts, $wpdb;
	if (count($bmIgnorePosts) > 0) {
		$where .= ' AND ' . $wpdb->posts . '.ID NOT IN(' . implode (',', $bmIgnorePosts) . ') ';
	}
	return $where;
}

add_filter ('posts_where', 'bm_postStrip');

Then to use this you would do your loop as normal, and call ‘bm_ignorePost($post->ID);’ for each post you want to ignore. The following example uses the same query twice, but will display totally different posts on each output.

<?php
// set the query
$query = 'posts_per_page=10';

// loop 1 - display most recent 10 posts
$queryObject = new WP_Query($query);
if ($queryObject->have_posts()) {
	while ($queryObject->have_posts()) {
		bm_ignorePost($queryPost->post->ID);
		$queryObject->the_post();
		the_title();
		the_content();
	}
}

// loop 2 - same query, get the next 10 posts
$queryObject = new WP_Query($query);
if ($queryObject->have_posts()) {
	while ($queryObject->have_posts()) {
		bm_ignorePost($queryPost->post->ID);
		$queryObject->the_post();
		the_title();
		the_content();
	}
}
?>

More?

There’s loads you can do with the query_posts command, this is only the tip of the iceberg, I’m sure I have missed some cool tips and tricks so let me know in the comments if you have any others.

Share

Join me, Barbio, Scott Carpenter, Sharon. They're all chatting about "10 WordPress query_posts tips you probably don’t know" below ›

Read Comments

93 Comments »

  1. Hi Ben. This is a solid list of use-cases for WP_Query. You should be thanked for drawing attention to the issues encountered by using query_posts. So, thanks.

    One small error I noticed – in a few examples you use the post_per_page argument, which should instead be the plural: posts_per_page.

    • oops – thanks for the catch there Matt, I shall update the code samples accordingly.

      Glad you think it’s all useful too :)

  2. This is awesome Ben. Perhaps you could update the actual codex article with some of these comments- or add an additional article to the codex for advanced query_posts functions. Again, superb post!

    • Hey Devin, I hadn’t considered that at all. I wonder if they’d be interested? I’m not sure how hard it is to contribute to the codex – guess it could be fun to try.

  3. Excellent stuff as always, Ben.

    I have been using query_posts across the board in the past, but I will do it right from this point on.

    Question, because you’ve already answered why we should use this superior method:

    Have you ever actually encountered any problems with query_posts that were solved using WP_Query? Or have you always done it “right”?

    • I’ve definitely not always done it the right way :)

      I used query_posts quite a bit when I first started with WP, it’s only in the last year or so that I did it “properly”. And yes – I’ve had a number of issues with query_posts that prompted me to start doing it properly. Most of the issues revolved around widgets and such that would pull up the data for other posts – and then trying to access the actual current post content.

  4. Thank you! Tip #1 just rescued me from the dire consequences of impulsive tinkering and hasty assumptions, and salvaged an investment in a couple of hours of coding. :-)

    Excellent post.

  5. I was using WP_Query in a function called from the sidebar. Later in the sidebar there are calls to next_post_link and previous_post_link, and these would return the wrong links after calling WP_Query, as if there was some interaction between my query (using $queryObj) and whatever else WordPress does in the background.

    It’s very possible I could have been doing other things wrong, but I was pretty stuck and grabbed on to the get_posts rope when it was offered. :-)

    • Oops – not sure how that one slipped through – thanks for pointing it out. I’ve updated the snippet so that it should work now.

  6. Great write-up: thanks.

    I’m stuck in a Sticky (Post) situation. My goal is to display this ….

    So I (obviously) was thinking I could call all stickies, show html, then call all non-stickies. But it’s not so simple (when you’re not so smart). And it might not be the best approach anyway.

    What approach would you take to this?

    I’ve seen a bit of chatter regarding “post__not_in” NOT working for stickies above v2.8 (http://wordpress.org/support/topic/311190?replies=7#post-1252984) and @wefixwp suggest that WordPress’s own documentation on it is wrong (http://twitter.com/wefixwp/statuses/5022493865), and I’ve had tht same experience.

  7. *sorry bout that – doesn’t like my code. (feel free to delete that last “blank” comment Ben, or just add this to my original comment and delete both 2nd & 3rd comments).

    My goal is to show this …

    - all sticky posts
    - HTML block
    - all non-sticky posts

  8. Thanks for this. I’ll be using it to get rid of the query_posts function I’m currently using to create a latest posts drop down box in the side bar.

  9. I have a quick question about the posts_per_page parameter. I’ve created a custom query. I’ve made it so that if an event has expired, then it’s not displayed in the query. My problem is that the posts_per_page parameter is counting my “expired” events. Therefore, If I set the parameter to 3 and 1 event is expired, the query actually only shows 2 events. How can I trick the query or fix this issue? If you want, I can send you the code that I’ve done. Thanks!

    • Interesting problem – a couple of questions. How are you expiring the events? How are events different to normal posts?

  10. I want to order a set of pages on the custom fields “x * y” (so the size).

    I was thinking how to do this using the posts_orderby filter can I include somehow the two custom fields multiplied (and +0′ed) to the query string?

    • found it!


      function edl_posts_join ($join) {
      global $edl_global_join;
      if ($edl_global_join) $join .= " $edl_global_join";
      return $join;
      }
      function edl_posts_orderby ($orderby) {
      global $edl_global_orderby;
      if ($edl_global_orderby) $orderby = $edl_global_orderby;
      return $orderby;
      }
      add_filter('posts_join','edl_posts_join');
      add_filter('posts_orderby','edl_posts_orderby');

      // specific
      $edl_global_join =
      "JOIN $wpdb->postmeta meta1 ON (meta1.post_id = $wpdb->posts.ID AND meta1.meta_key = 'X')" .
      "JOIN $wpdb->postmeta meta2 ON (meta2.post_id = $wpdb->posts.ID AND meta2.meta_key = 'Y')";
      $edl_global_orderby = " meta1.meta_value * meta2.meta_value DESC";
      query_posts('caller_get_posts=1');

      $wp_query = new WP_Query($args);

  11. I’ve been using this on a static homepage, but I can’t get the post’s comments to appear on the static page. ANy help?
    Thanks!

  12. Hi Ben,

    massive help…thanks,
    query posts has been messing my head for ages and today I decided to get to the bottom of it, fortunately, your post, the first I came across answered all my questions and more…

    guys like you make WordPress the ultimate tool for dumber guys like me ;)

  13. Fantastic list of awesome query_post tips, and very well-explained man. Thanks so much. I was struggling with getting WP-Coda to behave and playing with the query_posts parameters saved the day! Rock on!

  14. I’ve got a problem with WP_Query and I was wondering if someone could help.

    I’m registering a widget in the functions.php file of my theme, but the WP_Query command keeps returning all posts, while I am specifying a category. This is the code:


    add_action('init','initTVwidget');
    function initTVwidget() {
    if ( function_exists('register_sidebar_widget') )
    register_sidebar_widget(__('TV and player'), 'widget_tvAndCartoonPlayer');
    }

    function widget_tvAndCartoonPlayer() {
    $blog_posts = new WP_Query('showposts=2&cat=25');
    print_r($blog_posts);
    }

    Is there a scope problem? Because when I place the same WP_Query code snippet outside of the function in the functions.php it works.

  15. Is there a way to reorder the post with a link?

    The user clicks a link and say it goes to the http://www.website.com/category/categoryname. I use this to go to a page that only contains certain categories.

    But I want to reorder the posts according to what the user clicks on.
    If there are posts on a “services” category page and under them are “residential” and commercial and “real estate”. If the user (elsewhere on the site) clicks a link for residential I want to go to the services page but the residential post to go to the top.

    Is this possible by using a <a> ?

    Thx.

    • This all depends how your theme is coded. The link itself makes no difference, it’s where it links to that is important. If you want to have a page that links to different content then I would have three pages that display things differently.

  16. the ? in my last post at the bottom was supposed to read as a “”

    It got converted into a real one. sorry.

  17. Great tut thank you for making it all so clear. I am using a Custom Post and have a page to display the post using the following query

    $query = array (
    ‘posts_per_page’ => 5,
    ‘post_type’ => im_directory
    );
    $queryObject = new WP_Query($query);

    and then

    if ( $queryObject->max_num_pages > 1 ) : ?>

    <?php next_posts_link( __( '← Older posts’, ‘twentyten’ ) ); ?>
    <?php previous_posts_link( __( 'Newer posts →’, ‘twentyten’ ) ); ?>

    My problem is that pagination does not seem to work with WP_Query. Everything else works just fine so its rather frustrating. Is there a simple way around this?

  18. Question:

    How do you write HTML for styling the output in the loop?

    I’m trying to do it, but am obviously doing something wrong as nothing appears whenever I add stuff (like the_title(); makes it disappear).

    I know I’m missing something dumb, but if you could give an example of how you do it, that would be incredible.

    Thanks!

    -Adam

      • you can’t just embed html in php.

        something like this:

        <?php
        $query = 'posts_per_page=10';
        $queryObject = new WP_Query($query);
        // The Loop...
        if ($queryObject->have_posts()) {
        	while ($queryObject->have_posts()) {
        		$queryObject->the_post();
        ?>
        	<h1><?php the_title(); ?></h1>
        <?php
        		the_content();
        	}
        }
        ?>
  19. Hi,

    Thanks for this tutorial.

    I got a question: Is it possible set up a page to display the most viewed posts (Title AND Content)

    I know there’re plugins to do it but often they display only the title of the post with tags.

    Thanks for your help !

  20. Hi Ben,

    Great post. One question though:

    In #9 above you discuss

    I would like to access WP_Query and loop back through the posts from within a plugin. Do you have to have a separate loop code block within the plugin as well.

    In other words when wordpress loads it runs through the loop within its index file. To loop through the posts and modify them , I am thinking of rewinding the posts, then instantiating a new WP_Query object and looping through them again , within the plugin making modifications as I go. Does this make sense?

    KC

  21. Hello, thank you for the list! On #4 Meta Values you mentioned that you store votes in custom meta values. Will it be more efficient if you store votes in a separate database table? The postmeta table carries a lot of data. I ask because I’m developing a personal voting plugin and it’s pretty complicated querying and ordering from custom tables. Your method seems easier but I’m concerned about query speed issues.

    • To be honest I think this is a judgement call you will have to make. I did everything in post_meta because I could use the build in WordPress functions but if you plan to use the table a lot then you may be better off creating a brand new table designed for your project. I think both approaches have merit so you just have to plan as much as possible and make the decision based upon that.

  22. Ben,

    When I define a new custom query:
    `$publishedQuery = new WP_Query( $published_query_string );`

    I have not been able to figure out how to make the filter hook:
    `add_filter(‘posts_where’, ‘filter_where_test’);`

    The filter gets applied to the default wordpress queries, but not to my custom queries. Can yo help me out?

  23. Nice!! I was looking for a way to change the order of my posts. It can get quite frustrating to see the same written articles always at the top of your blog. This is a great way to show readers, different posts.

    • That’s a good point. I must admit I haven’t really used either of those a huge amount but they are definitely handy to know about.

  24. I have a question to pick your brains.

    I have 70 lessons one post for each. a lesson each week is delivered to customers.

    right now I have it set up as posts switching to pages is not a issue if needed.

    I would like each lesson to only display previous lessons in a side bar or whatever to the customer.

    so say there on lesson # 5 the list of previous lesson they could access would display only say lesson #1-4.

    as they move along the course say on lesson #20 it would only list #1-19 .

    is this easily done some how ?

    thank you.

    Jeff

  25. one thing that isnt well advertised within the wordpress docs is that you can set numerous post_types to be searched from within your query_post / new WP_Query.

    query_posts(array(‘post_type’=> array(‘post’, ‘page’))

  26. hi ben could you please help me ..i have querry_post function on my site home page and i have applied limit on it 3… but it showing all of the posts that i have …what should i do

  27. thank you so much dan for this long list of explanation.

    you save my day really i spent the whole night in fixing
    widget and ALL in one SEO PLUGIN conflict.
    i just change the query_posts way that is mentioned above in your post.
    and it worked very well still dont understand how it is fixed.
    i simple changed the way of query posts n tha’t
    any ways again thnkx buddy. :)
    one thing more how i can subscribe on your blog using email .

    • Glad you got your site working again.

      To subscribe by email click on the feed icon in the main nav and I give an email option to subscribe with.

  28. Hi Ben,

    I want to display all posts by category order but am struggling to find how. Following this post I have implemented WP_Query and have tried various orderby options with no success. Have scoured the codex and everywhere else with little success. Hope you can help.

  29. I’m using the Query Post plugin and it’s great, but I need to use it more than once. I have other categories i want in a separate widget. For instance, my first one is posts about writing, the second i want to do is posts about Relationships. How do i implement the use of this plugin more than once?

    • I am afraid I haven’t used the plugin so don’t know how it functions. If you make use of the code I described in the article then what you want to do should be pretty straight forward.

  30. Hi,

    is it possible to query logically?

    I mean something like that:
    query_posts(meta_key=monday&meta_value=1&meta_key=tuesday&meta_value=1&)

    Thanks and best regards…

  31. I found :

    $args = array(
    ‘author’ => $authorid,
    ‘showposts’ => 1,
    ‘caller_get_posts’ => 1,
    ‘order’=> ASC
    );
    $query = new WP_Query($args);

    thx ;)

  32. Hi, Thank you for your tips.
    I’m wondering how could i use (9. Rewind Posts) to solve an problem i have.

    I want to order my posts by a dynamic value, calculated inside the loop.

    It’s possible to order the posts by a calculated value inside the loop without updating anything in the database?
    (By performance reasons i wouldn’t want to update any meta_value )

    Thanks in advance for your help.

  33. Hi
    I have a similar question as HAYDEN from May 19, 2010…

    I have a quick question about the posts_per_page parameter. I’ve created a custom query. I’ve made it so that if an event has expired, then it’s not displayed in the query. My problem is that the posts_per_page parameter is counting my “expired” events. Therefore, If I set the parameter to 3 and 1 event is expired, the query actually only shows 2 events. How can I trick the query or fix this issue? If you want, I can send you the code that I’ve done. Thanks!

    My code…

    = time()-3600) {
    ?>

    The query is working but as posts expire, this list gets shorter because the numberposts=9 is sill counting expired posts!
    Is there any way of excluding posts that have expired from the loop so they are not counted?
    Any help would be much appreciated!
    Cheers,
    Mark.

  34. How would I use this to hide a specific post, using the id from all post query pages. My scenario is I’ve made a post and have featured it on my homepage. On the bottom of the homepage it pulls all of the latest posts. I don’t want it listed there. I also have another page that lists this post type and I don’t want it to show there either.

    Sorry if this is a simple fix, I’m not very fluent in php.

    Thanks!

    • You need to use 2 loops I think.
      1. A loop to get the posts that have that custom value , the eons you don’t want.
      2. The second loop using the post_not_in argument , using the returned ids from the first loop.

      • Thanks Barbio,
        I’m quite stuck with this. Can I ask/hire you to write this query?
        The custom field name is ‘city’ and the value would be ‘Melbourne’.
        So I need to exclude all posts with the ‘city’ custom field value of ‘Melbourne’.

        Or anyone else if Barbio isn’t available?

      • No problem Matt, if you can send me the php code you did till now for this part so it’s easier to adapt it.
        Please send me the details to may email. My email is my_name at gmail.com . And my name is barbio .

        If it’s quick i’m not even going to charge you for this.

        Best regards.

  35. The last four posts displays slider. Next on the list of entries I want to last posts without those that displays slider.
    So latest posts minus the first four.
    Can anyone help?

      • Yes I know it. I can now do well on the categories. However, I would do it automatically, not to have to constantly change the category posts.

  36. Couple of little updates on this

    1. You don’t need to do any casting as integer/float any more : 'orderby' => 'meta_value_num' now works to get the sort order you’d expect for int/float values instead of strings in meta fields. Tested on integers only for my site.

    2. Ignoring sticky posts is easier and more readable with 'ignore_sticky_posts' => true available now.

    -Simon

Comments are closed. Let's continue the conversation on Twitter.
css.php