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:
- It is easier to read
- You can more easily add conditions to the array using if statements to dynamically create queries
- 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.
- 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.
- Default – Select posts with Sticky Posts added first
- Select just Sticky Posts (maybe useful in Magazine themes?)
- 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.
Was it good/ useful/ a load of old rubbish? Let me know on Mastodon, or BlueSky (or Twitter X if you must).
Link to this page
Thanks for reading. I'd really appreciate it if you'd link to this page if you mention it in your newsletter or on your blog.