One of the most powerful features of WordPress is the WordPress Query. It is what determines what content is displayed on what page. And often you'll want to modify this query to your specific needs.
For example, you might want to:
- Exclude posts from a certain category on the homepage
- Increase or decrease the number of posts displayed per page for a specific post type
- Sort posts in your category archive by comment count rather than post date
- Exclude posts marked "no-index" in Yoast SEO from your on-site search results page
- List related articles at the end of a post.
If you're interested in looking "under the hood" at how queries in WordPress work, here's the slides from a presentation by WordPress Lead Developer Andrew Nacin. I'm going to focus this tutorial on common uses.
First, what not to do.
Don't use query_posts()
The query_posts() function completely overrides the main query on the page and can cause lots of issues. There is no instance where query_posts() is recommended.
Depending upon your specific requirements, you should create a new WP_Query or customize the main query .
If you need a simple way to list dynamic content on your site, try my Display Posts Shortcode plugin. It works just like the custom query described below but is built using a shortcode so you don't have to write any code or edit your theme files.
Customize the Main Query
The "main query" is whatever WordPress uses to build the content on the current page. For instance, on my Genesis category archive it's the 10 most recent posts in that category. The first four examples above all require altering the main query.
We'll use the WordPress hook pre_get_posts to modify the query settings before the main query runs. You must your function in your theme's functions.php or a core functionality plugin . WordPress needs to build the query to figure out what template to load, so if you put this in a template file like archive.php it will be too late.
All our functions are going to have a similar structure. First we're going to make sure we're accessing the main query. If we don't check this first our code will affect every query from nav menus to recent comments widgets. We'll do this by checking $query->is_main_query().
We'll also this code isn't running on admin queries. You might want to exclude a category from the blog for your visitors, but you still want to access those posts in the Posts section of the backend. To do this, we'll add ! is_admin().
Then we'll check to make sure the conditions are right for our modification. If you only want it on your blog's homepage, we'll make sure the query is for home ( $query->is_home() ). Here's a list of available conditional tags .
Finally, we'll make our modification by using the $query->set( 'key', 'value' ) method. To see all possible modifications you can make to the query, review the my WP_Query arguments guide or the WP_Query Codex page .
Exclude Category from Blog
/**
* Exclude Category from Blog
*
* @author Bill Erickson
* @link https://www.billerickson.net/customize-the-wordpress-query/
* @param object $query data
*
*/
function be_exclude_category_from_blog( $query ) {
if( $query->is_main_query() && ! is_admin() && $query->is_home() ) {
$query->set( 'cat', '-4' );
}
}
add_action( 'pre_get_posts', 'be_exclude_category_from_blog' );
We're checking to make sure the $query is the main query, and we're making sure we're on the blog homepage using is_home() . When those are true, we set 'cat' equal to '-4', which tells WordPress to exclude the category with an ID of 4.
Change Posts Per Page
Let's say you have a custom post type called Event. You're displaying events in three columns , so instead of the default 10 posts per page you want 18. If you go to Settings > Reading and change the posts per page, it will affect your blog posts as well as your events.
We'll use pre_get_posts to modify the posts_per_page only when the following conditions are met:
- On the main query
- Not in the admin area (we only want this affecting the frontend display)
- On the events post type archive page
/**
* Change Posts Per Page for Event Archive
*
* @author Bill Erickson
* @link https://www.billerickson.net/customize-the-wordpress-query/
* @param object $query data
*
*/
function be_change_event_posts_per_page( $query ) {
if( $query->is_main_query() && !is_admin() && is_post_type_archive( 'event' ) ) {
$query->set( 'posts_per_page', '18' );
}
}
add_action( 'pre_get_posts', 'be_change_event_posts_per_page' );
Modify Query based on Post Meta
This example is a little more complex. We want to make some more changes to our Event post type. In addition to changing the posts_per_page, we want to only show upcoming or active events, and sort them by start date with the soonest first.
I'm storing Start Date and End Date in postmeta as UNIX timestamps. With UNIX timestamps, tomorrow will always be a larger number than today, so in our query we can simply make sure the end date is greater than right now.
Here's more information on building Custom Metaboxes . My BE Events Calendar plugin is a good example of this query in practice.
If all the conditions are met, here's the modifications we'll do to the query:
- Do a meta query to ensure the end date is greater than today
- Order by meta_value_num (the value of a meta field)
- Set the 'meta_key' to the start date, so that's the meta field that posts are sorted by
- Put it in ascending order, so events starting sooner are before the later ones
/**
* Customize Event Query using Post Meta
*
* @author Bill Erickson
* @link http://www.billerickson.net/customize-the-wordpress-query/
* @param object $query data
*
*/
function be_event_query( $query ) {
if( $query->is_main_query() && !$query->is_feed() && !is_admin() && $query->is_post_type_archive( 'event' ) ) {
$meta_query = array(
array(
'key' => 'be_events_manager_end_date',
'value' => time(),
'compare' => '>'
)
);
$query->set( 'meta_query', $meta_query );
$query->set( 'orderby', 'meta_value_num' );
$query->set( 'meta_key', 'be_events_manager_start_date' );
$query->set( 'order', 'ASC' );
$query->set( 'posts_per_page', '4' );
}
}
add_action( 'pre_get_posts', 'be_event_query' ); Create a new query to run inside your page or template.
All of the above examples showed you how to modify the main query. In many instances, you'll want to run a separate query to load different information, and leave the main query unchanged. This is where Custom WordPress Queries are useful.
This is best when the content you're displaying is being loaded in addition to your current page's content. For instance, if you had a page about Spain and wanted to show your 5 most recent blog posts about Spain at the bottom, you could do something similar to this:
/**
* Display posts about spain
*
*/
function be_display_spain_posts() {
$loop = new WP_Query( array(
'posts_per_page' => 5,
'category_name' => 'spain',
) );
if( $loop->have_posts() ):
echo '<h3>Recent posts about Spain</h3>';
echo '<ul>';
while( $loop->have_posts() ): $loop->the_post();
echo '<li><a href="' . get_permalink() . '">' . get_the_title() . '</a></li>';
endwhile;
echo '</ul>';
endif;
wp_reset_postdata();
}
add_action( 'genesis_after_entry', 'be_display_spain_posts' ); For this you'll use the WP_Query class. For more information, see my post on Custom WordPress Queries . I also have a WP_Query Arguments reference guide.
No comments yet