Using pre_get_posts in WordPress

The pre_get_posts is a WordPress hook that allows us to easily modify queries anywhere on the WordPress site. When you are visiting any page of a WordPress site, the system creates a query that depends on the URL you are visiting. This is basically what the template hierarchy does. Now, this pre_get_posts hooks comes handy in order to modify the query before results are printed on your screen.

Technically, the pre_get_posts hook is called after the query variable object is created, but before the actual query is run. While using pre_get_posts hook, $query object is passed by reference, this means any changes we make to the object inside the function are made directly to the original object. In this way, you don’t need to declare globals variables or return a value. As per the WordPress.org Codex:

The pre_get_posts action gives developers access to the $query object by reference (any changes you make to $query are made directly to the original object – no return value is necessary).

Syntax:

<?php add_action( 'pre_get_posts', 'your_function_name' ); ?>

Examples:

Exclude categories on your main page
If you have 2 categories of posts (uncategorized ‘1’ and another ‘2’) that you don’t want to display on your ‘home’ blog page, you can use the following code in your plugin or theme to exclude posts from these categories:

/**
 * Excluding category id 1 and 2 in 'home' blog page
 * Alter the main loop
 * @uses pre_get_posts hook
*/
function textdomain_exclude_category( $query ) {
    if ( $query->is_home() && $query->is_main_query() ) {
        $query->set( 'cat', '-1,-2' );
    }
}
add_action( 'pre_get_posts', 'textdomain_exclude_category' );

Changing the number of posts per page, by post type
WordPress includes a single global setting for controlling the number of posts that appear on one loop page from your WordPress Dashboard at “Settings => Reading (Blog pages show at most)”. It is possible to create an action hook that changes / overrides the posts_per_page setting on a case-by-case basis. Best of all, this is done before the query is even executed (so there is no performance cost)!

/**
 * Changing the number of posts per page
 */
function textdomain_set_post_per_page( $query ) {
    //Don't change in WordPress admin
    if ( is_admin() || ! $query->is_main_query() )
        return;

    //Display only 1 post for the original blog archive
    if ( $query->is_main_query() && $query->is_home() ) {
        $query->query_vars['posts_per_page'] = 1;
        return;
    }
    //Display 20 posts for a custom post type called 'movie'
    if ( $query->is_main_query() && $query->is_post_type_archive( 'movie' ) ) {
        $query->query_vars['posts_per_page'] = 20;
        return;
    }
}
add_action( 'pre_get_posts', 'textdomain_set_post_per_page', 1 );

Include Custom Post Types in Search Results
The following code will include posts from Custom Post type “movie” in Search Results

/**
 * Include Movie post type in Search
 */
function textdomain_include_search($query) {
    if ( !is_admin() && $query->is_main_query() ) {
        if ($query->is_search) {
            $query->set('post_type', array( 'post', 'movie' ) );
        }
    }
}
add_action( 'pre_get_posts', 'textdomain_include_search' );

Working with post type and meta fields
The following code displays posts from a specific type “movie” and with a custom meta field key “movie_type'” equal to a determined value “action”.

/**
 * Display Action Movies
 */
function textdomain_action_movies($query) {
    if ( !is_admin() && $query->is_main_query() ) {
        if ( $query->query_vars['post_type'] == 'movie' ) {
            $query->set( 'meta_key', 'movie_type' );
            $query->set( 'meta_value', 'action' );
        }
    }
}
add_action( 'pre_get_posts', 'textdomain_action_movies' );

Displaying Posts in your Search Results page
This is useful if you have search box in your blog. People will use your blog search box to search your blog post and not the pages. You can add the following code in your theme or plugin to filter search to display posts.

/**
 * Display posts in search result
 */
function textdomain_search_posts($query) {
    if ( !is_admin() && $query->is_main_query() ) {
        if ($query->is_search) {
            $query->set('post_type', 'post');
        }
    }
}
add_action( 'pre_get_posts', 'textdomain_search_posts' );

Working with Dates
You can also work with Dates. For example, the following code will display the posts from current date. Similarly, you can change the date as per you need.

function textdomain_posts_by_date( $query ) {
    if ( !is_admin() && $query->is_main_query() ) {
        $today = getdate();
        $query->set( 'year', $today['year'] );
        $query->set( 'monthnum', $today['mon'] );
        $query->set( 'day', $today['mday'] );
    }
}
add_action( 'pre_get_posts', 'textdomain_posts_by_date' );

You can also work with the range of dates. Like the following code will display posts older than the current date.

/**
 * Create date filter for posts older then current date
 */
function textdomain_date_range( $where = '' ) {
    $today = date( 'Y-m-d' );
    $where .= " AND post_date < '$today'";
    return $where;
}
/**
 * Displays the Post older then current date
 * @uses posts_where filer for data range
 * @uses pre_get_posts hook
 */
function textdomain_older_posts( $query ) {
    if ( !is_admin() && $query->is_main_query() ) {
        $query->set( 'order', 'ASC' );
        add_filter( 'posts_where', 'textdomain_date_range' );
    }
    return $query;
}
add_action( 'pre_get_posts', 'textdomain_older_posts' );

Note:

We have to be very careful while using pre_get_posts hook because this hook is fired for every WordPress query

  • get_posts()
  • new wp_query()
  • random recent posts widget
  • The RSS Feed
  • The Dashboard
  • …and so on

So to avoid this we use conditional tag $query->is_main_query() then filter will fire only on main loop. This filter affects admin screen queries also. So it will be better to make sure is_admin() return false.

4 thoughts on “Using pre_get_posts in WordPress”

  1. Very helpful post.

    One little thing: in the last example with the ‘post_where’ filter, shouldn’t the line

    AND post_date >= ‘$today’

    be

    AND post_date < '$today'

    if you want to get posts older than today's posts?

Leave a Reply

Your email address will not be published. Required fields are marked *