search ]

How to Modify the WordPress Loop with pre_get_posts

One of the most powerful features of WordPress is the WP_Query. It is the query that determines which content is displayed and on which page. You will often want to modify this query to fit your specific needs, for example:

  • Exclude posts from a specific category.
  • Change the number of posts displayed per page for a custom post type.
  • Control which posts appear and in what order.
  • Create a separate loop to display different content within a specific page.

You can modify the loop using the pre_get_posts hook that WordPress provides. This guide does not dive deep into what happens behind the scenes, but it covers several practical ways to modify the loop using pre_get_posts.

Options for Modifying the WordPress Loop

Before getting started, it is important to note that you should never use query_posts(). As the WordPress Developer Reference makes clear, there are numerous warnings against it. Instead, there are two approaches depending on your needs.

1. Creating a New Loop Inside a Page Template

This approach is best when the content you want to display loads alongside the current page content. For example, if you have a page about a specific topic and you want to show the five most recent posts on that topic at the bottom of the page, creating a new loop is the right choice.

When creating multiple loops on the same page, it is important to reset the loop after use by calling wp_reset_postdata(). This restores the global $post variable to the current post in the main query.

2. Modifying the Main Query

If you want to change the content displayed on a specific page, you most likely want to modify the main query (Main Query). The first three examples listed above all require modifying the main query.

WordPress provides a useful hook called pre_get_posts. This hook fires after the query variables have been parsed but right before the actual query runs. This is where you step in and change the query parameters.

This hook must be used from the functions.php file (or from a plugin). WordPress builds the query to determine which page template to load, so if you use the hook inside a template file, it will be too late.

Make sure you are using a child theme or a must-use plugin (MU-Plugin) before adding custom code to functions.php. Never edit the parent theme’s functions.php directly.

For a deeper look at how WordPress decides which template file to use, check out the guide on WordPress template hierarchy.

All the functions demonstrated here follow a similar structure. First, verify that you are targeting the main query. If you skip this step, your code will affect every query on the page – from WordPress menus to a sidebar widget displaying recent comments.

You can do this by calling $query->is_main_query().

Next, make sure the conditions are correct for the modification you want to apply. For example, if you only want to apply the change on the blog homepage, check that the query belongs to the home page: $query->is_home().

Finally, apply the change using the method $query->set( 'key', 'value' ). For a full list of available parameters, see the WP_Query Reference.

Practical Examples

Here are several common scenarios where you would use pre_get_posts to modify the main WordPress loop.

Exclude a Specific Category from the Blog

function my_exclude_category_from_blog( $query ) {
    if ( $query->is_main_query() && $query->is_home() ) {
        $query->set( 'cat', '-4' );
    }
}
add_action( 'pre_get_posts', 'my_exclude_category_from_blog' );

The code above verifies that the query is the main query and that you are on the blog homepage using is_home(). When both conditions are met, the category ID is set to '-4', which tells WordPress to exclude that category.

Change the Number of Posts Per Page

Suppose you have a custom post type called events that displays events. You show events in three columns on your site, so instead of the default 10 posts per page you want to display 12.

If you go to Settings > Reading in the dashboard and change the number of posts per page, it will affect all post types across the blog, including events. Use pre_get_posts to modify the posts_per_page setting only when these conditions are met:

  • The query is the main WordPress query.
  • You are not in the WordPress admin dashboard (the change should only affect the front-end).
  • You are on the events archive page.
function my_change_event_posts_per_page( $query ) {
    if ( $query->is_main_query() && ! is_admin() && is_post_type_archive( 'event' ) ) {
        $query->set( 'posts_per_page', '12' );
    }
}
add_action( 'pre_get_posts', 'my_change_event_posts_per_page' );

Modify the Loop Based on Meta Fields

This is a slightly more advanced example. This time you will modify the events post type so that in addition to changing the number of posts per page, only future events are displayed (not past ones), and the nearest events appear first.

Store the event start and end dates as unix timestamps. Tomorrow will always have a higher value than today, so in the loop you can simply check whether the event end date is greater than the current timestamp.

For more details on meta queries in the loop, check out the guide on filtering posts by meta fields with WP_Query.

If all conditions are met, the following changes are applied to the loop:

  • A meta query ensures the event end date is greater than the current timestamp.
  • Posts are ordered by meta_value_num (the numeric meta field value).
  • The meta_key is set to the start date, which becomes the field used for sorting.
  • Posts are displayed in ascending order so the nearest events appear first.
function my_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' );
    }
}
add_action( 'pre_get_posts', 'my_event_query' );

FAQs

Common questions about modifying the WordPress loop:

What is the difference between pre_get_posts and a new WP_Query?
pre_get_posts modifies the main query (Main Query) before it runs. A new WP_Query creates an additional loop and adds a separate database query. Use pre_get_posts when you want to change the existing content on a page, and a new WP_Query when you want to add extra content.
Why should I avoid using query_posts()?
query_posts() replaces the main query entirely, which causes issues with pagination, plugins that rely on the main query, and conditional tags. It also runs an unnecessary additional database query. pre_get_posts simply adjusts the parameters of the main query before it runs, with no extra query overhead.
Does pre_get_posts affect the admin dashboard too?
Yes, if you do not add a condition that checks you are not in the admin. Always add ! is_admin() to your function's conditions to prevent it from affecting the admin dashboard, unless you intentionally want to modify the query there as well.
Can I use pre_get_posts to change the post order?
Absolutely. Use $query->set( 'orderby', 'date' ) and $query->set( 'order', 'ASC' ) to change the sort order. You can sort by date, title, ID, meta field value, random order, and more.
Do pre_get_posts modifications affect site performance?
Using pre_get_posts has virtually no performance impact because it modifies an existing query instead of creating a new one. However, complex meta queries (like the events example) may slow down the query if there are many records and no proper index on the meta fields in the database.

Summary

The pre_get_posts hook is a powerful tool that allows you to modify nearly every aspect of the main loop in WordPress.

The key points to remember: always verify you are modifying the main query only, add a ! is_admin() condition to avoid affecting the admin dashboard, and use this hook from functions.php rather than from a template file.

Join the Discussion
0 Comments  ]

Leave a Comment

To add code, use the buttons below. For instance, click the PHP button to insert PHP code within the shortcode. If you notice any typos, please let us know!

Savvy WordPress Development official logo