Introduction to WP_Query :
WP_Query is a class defined in wp-includes/query.php that deals with the intricacies of a post’s (or page’s) request to a WordPress blog. The wp-blog-header.php (or the WP class in Version 2.0) gives the $wp_query object information defining the current request, and then $wp_query determines what type of query it’s dealing with (possibly a category archive, dated archive, feed, or search), and fetches the requested posts. It retains a lot of information on the request, which can be pulled at a later date.
If you’ve built simple themes in the past, chances are the need to retrieve posts on your own didn’t arise. After all, WordPress loads the most recent 10 posts on the main page and the correct posts on archive pages. What about more complex scenarios? What if you would like a sitemap of sorts and you want to list all your posts and pages ordered by date?
WP_Query is your friend, it allows you to pull posts from the database according to your criteria. You can retrieve all posts from a single category that contain certain tags as well. You could retrieve all pages created last year and posts which do not have a featured image.
In this post I will give you an in-depth look into how WP_Query works – let’s get started!
Basic Usage :
<?php /** * WordPress Query Comprehensive Reference * Compiled by luetkemj - luetkemj.com * * CODEX: http://codex.wordpress.org/Class_Reference/WP_Query * Source: http://core.trac.wordpress.org/browser/tags/3.3.1/wp-includes/query.php */ $args = array( //////Author Parameters - Show posts associated with certain author. 'author' => 1,2,3, //(int) - use author id [use minus (-) to exclude authors by ID ex. 'author' => -1,-2,-3,] 'author_name' => 'luetkemj', //(string) - use 'user_nicename' (NOT name) //////Category Parameters - Show posts associated with certain categories. 'cat' => 5,//(int) - use category id. 'category_name' => 'staff', 'news', //(string) - use category slug (NOT name). 'category__and' => array( 2, 6 ), //(array) - use category id. 'category__in' => array( 2, 6 ), //(array) - use category id. 'category__not_in' => array( 2, 6 ), //(array) - use category id. //////Tag Parameters - Show posts associated with certain tags. 'tag' => 'cooking', //(string) - use tag slug. 'tag_id' => 5, //(int) - use tag id. 'tag__and' => array( 2, 6), //(array) - use tag ids. 'tag__in' => array( 2, 6), //(array) - use tag ids. 'tag__not_in' => array( 2, 6), //(array) - use tag ids. 'tag_slug__and' => array( 'red', 'blue'), //(array) - use tag slugs. 'tag_slug__in' => array( 'red', 'blue'), //(array) - use tag slugs. //////Taxonomy Parameters - Show posts associated with certain taxonomy. //Important Note: tax_query takes an array of tax query arguments arrays (it takes an array of arrays) //This construct allows you to query multiple taxonomies by using the relation parameter in the first (outer) array to describe the boolean relationship between the taxonomy queries. 'tax_query' => array( //(array) - use taxonomy parameters (available with Version 3.1). 'relation' => 'AND', //(string) - Possible values are 'AND' or 'OR' and is the equivalent of ruuning a JOIN for each taxonomy array( 'taxonomy' => 'color', //(string) - Taxonomy. 'field' => 'slug', //(string) - Select taxonomy term by ('id' or 'slug') 'terms' => array( 'red', 'blue' ), //(int/string/array) - Taxonomy term(s). 'include_children' => true, //(bool) - Whether or not to include children for hierarchical taxonomies. Defaults to true. 'operator' => 'IN' //(string) - Operator to test. Possible values are 'IN', 'NOT IN', 'AND'. ), array( 'taxonomy' => 'actor', 'field' => 'id', 'terms' => array( 103, 115, 206 ), 'include_children' => false, 'operator' => 'NOT IN' ) ), //////Post & Page Parameters - Display content based on post and page parameters. 'p' => 1, //(int) - use post id. 'name' => 'hello-world', //(string) - use post slug. 'page_id' => 1, //(int) - use page id. 'pagename' => 'sample-page', //(string) - use page slug. 'pagename' => 'contact_us/canada', //(string) - Display child page using the slug of the parent and the child page, separated ba slash 'post_parent' => 1, //(int) - use page id. Return just the child Pages. 'post__in' => array(1,2,3), //(array) - use post ids. Specify posts to retrieve. 'post__not_in' => array(1,2,3), //(array) - use post ids. Specify post NOT to retrieve. //NOTE: you cannot combine 'post__in' and 'post__not_in' in the same query //////Type & Status Parameters - Show posts associated with certain type or status. 'post_type' => array( //(string / array) - use post types. Retrieves posts by Post Types, default value is 'post'; 'post', // - a post. 'page', // - a page. 'revision', // - a revision. 'attachment', // - an attachment. The default WP_Query sets 'post_status'=>'published', but atchments default to 'post_status'=>'inherit' so you'll need to set the status to 'inherit' or 'any'. 'my-post-type', // - Custom Post Types (e.g. movies) ), 'post_status' => array( //(string / array) - use post status. Retrieves posts by Post Status, default value i'publish'. 'publish', // - a published post or page. 'pending', // - post is pending review. 'draft', // - a post in draft status. 'auto-draft', // - a newly created post, with no content. 'future', // - a post to publish in the future. 'private', // - not visible to users who are not logged in. 'inherit', // - a revision. see get_children. 'trash' // - post is in trashbin (available with Version 2.9). ), //NOTE: The 'any' keyword available to both post_type and post_status queries cannot be used within an array. 'post_type' => 'any', // - retrieves any type except revisions and types with 'exclude_from_search' set to true. 'post_status' => 'any', // - retrieves any status except those from post types with 'exclude_from_search' set to true. //////Pagination Parameters 'posts_per_page' => 10, //(int) - number of post to show per page (available with Version 2.1). Use 'posts_per_page'=-1 to show all posts. Note if the query is in a feed, wordpress overwrites this parameter with the stored 'posts_per_rss' option. Treimpose the limit, try using the 'post_limits' filter, or filter 'pre_option_posts_per_rss' and return -1 'posts_per_archive_page' => 10, //(int) - number of posts to show per page - on archive pages only. Over-rides showposts anposts_per_page on pages where is_archive() or is_search() would be true 'nopaging' => false, //(bool) - show all posts or use pagination. Default value is 'false', use paging. 'paged' => get_query_var('page'), //(int) - number of page. Show the posts that would normally show up just on page X when usinthe "Older Entries" link. //NOTE: You should set get_query_var( 'page' ); if you want your query to work with pagination. Since WordPress 3.0.2, you dget_query_var( 'page' ) instead of get_query_var( 'paged' ). The pagination parameter 'paged' for WP_Query() remains the same. //////Offset Parameter 'offset' => 3, //(int) - number of post to displace or pass over. //////Order & Orderby Parameters - Sort retrieved posts. 'order' => 'DESC', //(string) - Designates the ascending or descending order of the 'orderby' parameter. Defaultto 'DESC'. //Possible Values: //'ASC' - ascending order from lowest to highest values (1, 2, 3; a, b, c). //'DESC' - descending order from highest to lowest values (3, 2, 1; c, b, a). 'orderby' => 'date', //(string) - Sort retrieved posts by parameter. Defaults to 'date'. //Possible Values:// //'none' - No order (available with Version 2.8). //'ID' - Order by post id. Note the captialization. //'author' - Order by author. //'title' - Order by title. //'date' - Order by date. //'modified' - Order by last modified date. //'parent' - Order by post/page parent id. //'rand' - Random order. //'comment_count' - Order by number of comments (available with Version 2.9). //'menu_order' - Order by Page Order. Used most often for Pages (Order field in the EdiPage Attributes box) and for Attachments (the integer fields in the Insert / Upload MediGallery dialog), but could be used for any post type with distinct 'menu_order' values (theall default to 0). //'meta_value' - Note that a 'meta_key=keyname' must also be present in the query. Note alsthat the sorting will be alphabetical which is fine for strings (i.e. words), but can bunexpected for numbers (e.g. 1, 3, 34, 4, 56, 6, etc, rather than 1, 3, 4, 6, 34, 56 as yomight naturally expect). //'meta_value_num' - Order by numeric meta value (available with Version 2.8). Also notthat a 'meta_key=keyname' must also be present in the query. This value allows for numericasorting as noted above in 'meta_value'. //'post__in' - Preserve post ID order given in the post__in array (available with Version 3.5). //////Sticky Post Parameters - Show Sticky Posts or ignore them. 'ignore_sticky_posts' => false, //(bool) - ignore sticky posts or not. Default value is false, don't ignore. Ignore/excludsticky posts being included at the beginning of posts returned, but the sticky post will still be returned in the natural order othat list of posts returned. //NOTE: For more info on sticky post queries see: http://codex.wordpress.org/Class_Reference/WP_Query#Sticky_Post_Parameters //////Time Parameters - Show posts associated with a certain time period. 'year' => 2012, //(int) - 4 digit year (e.g. 2011). 'monthnum' => 3, //(int) - Month number (from 1 to 12). 'w' => 25, //(int) - Week of the year (from 0 to 53). Uses the MySQL WEEK command. The mode is dependenon the "start_of_week" option. 'day' => 17, //(int) - Day of the month (from 1 to 31). 'hour' => 13, //(int) - Hour (from 0 to 23). 'minute' => 19, //(int) - Minute (from 0 to 60). 'second' => 30, //(int) - Second (0 to 60). //////Custom Field Parameters - Show posts associated with a certain custom field. 'meta_key' => 'key', //(string) - Custom field key. 'meta_value' => 'value', //(string) - Custom field value. 'meta_value_num' => 10, //(number) - Custom field value. 'meta_compare' => '=', //(string) - Operator to test the 'meta_value'. Possible values are '!=', '>', '>=', '<', or ='. Default value is '='. 'meta_query' => array( //(array) - Custom field parameters (available with Version 3.1). array( 'key' => 'color', //(string) - Custom field key. 'value' => 'blue', //(string/array) - Custom field value (Note: Array support is limited to a compare value of 'IN', 'NOT IN', 'BETWEEN', or 'NOT BETWEEN') 'type' => 'CHAR', //(string) - Custom field type. Possible values are 'NUMERIC', 'BINARY', 'CHAR', 'DATE', 'DATETIME', 'DECIMAL', 'SIGNED', 'TIME', 'UNSIGNED'. Default value is 'CHAR'. 'compare' => '=', //(string) - Operator to test. Possible values are '=', '!=', '>', '>=', '<', '<=', 'LIKE', 'NOT LIKE', 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN'. Default value is '='. ), array( 'key' => 'price', 'value' => array( 1,200 ), 'compare' => 'NOT LIKE' ) //////Permission Parameters - Display published posts, as well as private posts, if the user has the appropriate capability: 'perm' => 'readable' //(string) Possible values are 'readable', 'editable' (possible more ie all capabilitiealthough I have not tested) //////Parameters relating to caching 'no_found_rows' => false, //(bool) Default is false. WordPress uses SQL_CALC_FOUND_ROWS in most queries in order timplement pagination. Even when you don’t need pagination at all. By Setting this parameter to true you are telling wordPress not tcount the total rows and reducing load on the DB. Pagination will NOT WORK when this parameter is set to true. For more informatiosee: http://flavio.tordini.org/speed-up-wordpress-get_posts-and-query_posts-functions 'cache_results' => true, //(bool) Default is true 'update_post_term_cache' => true, //(bool) Default is true 'update_post_meta_cache' => true, //(bool) Default is true //NOTE Caching is a good thing. Setting these to false is generally not advised. For more info on usage see: http://codex.wordpresorg/Class_Reference/WP_Query#Permission_Parameters //////Search Parameter 's' => $s, //(string) - Passes along the query string variable from a search. For example usage see: http://www.wprecipes.com/how-to-display-the-number-of-results-in-wordpress-search 'exact' => true //(bool) - flag to make it only match whole titles/posts - Default value is false. For more information see: https://gist.github.com/2023628#gistcomment-285118 'sentence' => true //(bool) - flag to make it do a phrase search - Default value is false. For more information see: https://gist.github.com/2023628#gistcomment-285118 //////Post Field Parameters //Not sure what these do. For more info see: http://codex.wordpress.org/Class_Reference/WP_Query#Post_Field_Parameters //////Filters //For more information on available Filters see: http://codex.wordpress.org/Class_Reference/WP_Query#Filters ); $the_query = new WP_Query( $args ); // The Loop if ( $the_query->have_posts() ) : while ( $the_query->have_posts() ) : $the_query->the_post(); // Do Stuff endwhile; endif; // Reset Post Data wp_reset_postdata(); ?>
Custom Loop :
The key to working well with custom queries is mastering the arguments you can pass to them. Before we look at this, let’s create a quick “skeleton” we can use to list posts. The loop on a regular archive page would look something like this:
<?php if ( have_posts() ) : ?> <?php while( have_posts() ) : the_post() ?> <!-- Display Post Here --> <?php endwhile ?> <?php else : ?> <!-- Content If No Posts --> <?php endif ?>
This works just fine because before the page is loaded, WordPress has already retrieved the correct posts. When we write a custom query we’ll need a custom loop. The code is very similar, here goes:
<?php $args = array( 'post_type' => 'post', 'post_status' => 'future' ); $scheduled = new WP_Query( $args ); if ( $scheduled->have_posts() ) : ?> <?php while( $scheduled->have_posts() ) : $scheduled->the_post() ?> <!-- Display Post Here --> <?php endwhile ?> <?php else : ?> <!-- Content If No Posts --> <?php endif ?>
We create a new WordPress query using the WP_Query class, passing it parameters to specify the types of posts we need. We then call the have_posts() and the_post() methods on our $scheduled object.
If you’re new to object oriented programming and don’t really understand why, don’t worry, you can still easily use custom queries. We’ll be focusing on the $args array throughout the post which will be pretty straightforward.
Simple Arguments :
As I mentioned, the main “body of knowledge” when it comes to WP_Query lies in its arguments. The Codex lists all of them, we’ll take a look at the most useful ones here.
Some arguments are pretty simple, such as the tag or tag_id parameter. The former takes a tag slug, the later a tag id. You can also separate multiple items with commas or use negative ids to indicate that you want to retrieve posts that do not have that particular tag attached.
$args = array( 'post_type' => 'post', 'tag_id' => '22,92,44,-21' ); $our_posts = new WP_Query( $args );
The snippet would retrieve articles which have any of the first three tags attached to them, but not the fourth. While this system is powerful enough for simple needs, I usually use the far more flexible taxonomy query which I’ll discuss a bit later.
The author, author_name, cat, category_name, s (for search terms), post_status, post_type are examples of some more simple fields. Note that some fields, like post_status require you to pass an array of statuses if you want to use multiple values.
$args = array( 'post_type' => array( 'post', 'page' ), 'post_status' => array( 'draft', 'publish' ), 'cat' => 'music,videos' ); $our_posts = new WP_Query( $args );
The example above retrieves any posts that are either pages, posts; are either published or in draft status and have either the music or videos category assigned. This could be used for displaying the titles of any current or planned content related to music or videos.
Taxonomy Queries :
For simple stuff, using the mentioned arguments to retrieve posts based on categories or tags is enough but what if you have a custom taxonomy, or you need to combine multiple parameters? The answer lies in the tax_query argument which is itself an array. Let’s look at an example first.
$args = array( 'post_type' => 'book', 'tax_query' => array( array( 'taxonomy' => 'genre', 'field' => 'slug', 'terms' => array( 'scifi', 'thriller' ), 'operator' => 'NOT IN', ), array( 'taxonomy' => 'author', 'field' => 'id', 'terms' => array( 92, 883, 399 ), ), 'relation' => 'AND', ), ); $query = new WP_Query( $args );
Here we retrieve some posts from our ‘book’ post type, adding some taxonomy parameters. Using two arrays defined within the tax_query we’ve specified that we want to pull books written by author’s 92, 883 or 399 which are not in the sci-fi or thriller genre. The relation parameter defines the relationship between the two arrays. In our case it is an ‘and’ relationships which means that both conditions must be met.
If we used ‘or’ we would have retrieved books which are either not in the sci-fi or thriller category or they are written by the specified three authors. In plain English this means: I want to see all my books except for the sci-fi and thriller genre. However, I want to see all books from Terry Pratchett, Douglas Adams and Stephen King, regardless of genre.
Meta Queries :
Meta queries are very similar to taxonomy queries in structure but they use data from the post meta table to filter posts. The code below will display all published posts which have a featured image.
$args = array( 'post_type' => 'post', 'meta_query' => array( array( 'key' => '_thumbnail_id', 'value' => '', 'compare' => '!=', ), ), ); $query = new WP_Query( $args );
WordPress stores the ID of the featured image for a post using the _thumbnail_id field. So if we retrieve all posts where this meta value is not empty, we should end up with all posts which have a featured image.
The secret here is knowing how to use the compare property and type. You can use a multitude of values which should be familiar from SQL: ‘=’, ‘!=’, ‘>’, ‘>=’, ‘<', '<=', 'LIKE', 'NOT LIKE', 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN', 'EXISTS' (from WordPress 3.5 and up), and 'NOT EXISTS' (from WordPress 3.5 and up) The type property is usually important when you want to compare numbers or dates. Possible values are ‘NUMERIC’, ‘BINARY’, ‘CHAR’, ‘DATE’, ‘DATETIME’, ‘DECIMAL’, ‘SIGNED’, ‘TIME’, ‘UNSIGNED’. Keep in mind to use NUMERIC when comparing numbers, in most other cases you should be fine. Just like with taxonomy queries you can stack multiple requirements and then use relation parameter to specify the relationship between them. [php] $args = array( 'post_type' => 'post', 'meta_query' => array( 'relation' => 'OR', array( 'key' => 'mood', 'value' => array( 'happy', 'awesome' ), 'compare' => 'IN', ), array( 'key' => 'income', 'value' => 500, 'compare' => '>', 'type' => 'NUMERIC' ), ), ); $query = new WP_Query( $args ); [/php] The example above would display posts from a hypothetical diary where something good happened. I either felt happy or awesome or I made more than $500 that day. Date Parameters :
Dates can get a bit complex, but are very flexible. The WP_Query Documentation has plenty of examples if you need more information.
Retrieving posts from a certain time is pretty easy. You can use the year, monthnum, w (week of the year), day and a couple of other parameters to specify a time. The code below would retrieve all posts from the March of 2013.
$args = array( 'post_type' => 'post', 'year' => 2013, 'month' => 3 ); $query = new WP_Query( $args );
If you want to get into more complex time ranges you’ll need to use date_query. This allows you to specify arbitrary ranges quite easily, using a familiar array format. The following example – courtesy of the Codex – shows how you can retrieve posts which were written between 9AM and 5PM on weekdays.
$args = array( 'post_type' => 'post', 'date_query' => array( array( 'hour' => 9, 'compare' => '>=', ), array( 'hour' => 17, 'compare' => '<=', ), array( 'dayofweek' => array( 2, 6 ), 'compare' => 'BETWEEN', ), ), ); $query = new WP_Query( $args );
Ordering Results :
The order in which a list is presented is just as important as the elements within. Thankfully WordPress offers us the order_by and order parameters. The order parameter is pretty straightforward, you can use ASC or DESC to order ascending or descending.
The order_by parameter can take on a number of values: ‘none’, ‘ID’, ‘author’, ‘title’, ‘name’, ‘type’, ‘date’, ‘modified’, ‘parent’, ‘rand’, ‘comment_count’, ‘menu_order’, ‘meta_value’, ‘meta_value_num’, ‘post__in’. Ordering by meta_value can be especially useful. In this case, don’t forget to specify the meta_key field as well.
$args = array( 'post_type' => 'painting', 'meta_query' => array( array( 'key' => 'price', 'value' => 50000, 'compare' => '>', 'type' => 'NUMERIC' ), ), 'order_by' => 'meta_value_num', 'meta_key' => 'price' ); $query = new WP_Query( $args );
This example retrieves paintings which are valued over $50,000 and orders them ascending by price. Note that since the price is stored as a number I used meta_value_num to order the results, as opposed to meta_value which would be used for strings.
Another ordering worth a mention is post__in. This post__in is a parameter in its own right, it allows you to specify an array of post ids to retrieve. By default, WordPress orders posts by date, the order you specified in the post__in is not preserved. If you use post__in as the order by value as well, WordPress will preserve the order.
$args = array( 'post_type' => 'post', 'post__in' => array( 23, 441, 12, 322, 34, 33), 'order_by' => 'post__in', ); $query = new WP_Query( $args );