There are a lot of ways to query posts in WordPress and a lot of parameters you can use for pulling in just the data you want. For example, you can easily pull in just posts that are in category X, Y, or Z, and you can also just as easily pull in only posts that have a particular meta field set to some value or other. This tutorial video is going to focus on demonstrating the correct way to modify queries and also show how to easily query posts based on meta fields.

One of the worst mistakes that many, many developers make (I’ve been guilty of it countless times) is using query_posts() to modify the main query. One particular example I can think of is using query_posts() in search.php to modify the search results.

Another “crime” that a lot of developers, myself included, are guilty of is modifying queries with a disregard for other queries that might be running on the page. For example, a lot of theme developers like to add an option to their admins for controlling the number of posts displayed on archive pages. A lot of times what happens here is the custom posts_per_page parameter the developer sets overrides ALL queries on the page. I’ve seen this happen several times with my Sugar FAQs plugin; the posts_per_page parameter set by the theme overrides the posts_per_page set in the get_posts() query for the FAQs, so instead of showing 15 questions, the short code only shows 5.

There are many other “crimes” that get committed when queries are involved, but we don’t need to go through. Suffice it to say that the correct way to modify queries anywhere on your WordPress site is through the pre_get_posts action hook (also available as a filter).

Discussed in depth in the video, the pre_get_posts action allows us to easily modify queries anywhere on the site. Here is a quick example of how to force the front page to only show posts that have a meta key called “ecpt_color” that is set to a value of “green”.

function pw_filter_query( $query ) {
	if( is_front_page() && is_main_query() && $query->query_vars['post_type'] != 'nav_menu_item' ) {
		$query->set('meta_key', 'ecpt_color');
		$query->set('meta_value', 'green');
add_action('pre_get_posts', 'pw_filter_query', 9999);
Signup as a premium subscriber to gain full access to the video.
Join Now for $6

Join now to gain access to this tutorial and more.

Join Now
  1. yellowhousedesign

    I’m so glad you published this tut, exactly what I was looking for. Now all I’ll do is hook-up a little front-end form for users to choose their filter options and I’ll be in good shape! Looks like pre_get_posts() doesn’t have sort options – so I may need to dig into that a bit more (it’s saying use query_posts() for that). Thank you again very much!

    • Pippin

      You mean sort options for setting the order items are displayed? Or what they are sorted by?

    • Pippin

      See Paul’s comment below. Is that what you’re referring to?

    • yellowhousedesign

      I believe so – ill give that a shot — I need to sort by a meta value as well, so I’ll take another look at how that is done in the tut. Thanks!

    • Pippin

      You shouldn’t have any problems sorting by a meta value. Use sort=meta_key and orderby=meta_value.

      If your meta values are numeric, then use orderby=meta_valu_num

  2. paul

    so do yo uknow the reason why on the codex, one example uses query->set and the other changes the query_vars object?
    for example to change the post order, I used this

    • Pippin

      I really don’t know. As far as I know, they do exactly the same thing.

    • Pippin

      Exploring Core a bit I think I’ve found the answer.
      $query->set() is defined as this:

      function set($query_var, $value) {
      	$this->query_vars[$query_var] = $value;

      So that means that $query->set() is doing nothing more than assigning what you pass to the query_vars array.

      We could use $query->get() for doing the same thing as $query->query_vars[‘parameter’] with $query-&t;get(), which is defined as this:

      function get($query_var) {
      	if ( isset($this->query_vars[$query_var]) )
      		return $this->query_vars[$query_var];
      	return '';

      So no, there is really no difference at all.

    • Brady Vercher

      Aside from the two methods mentioned, there’s actually a third function to do the same thing: set_query_var( $var, $value ).

    • Pippin

      Oh that’s cool. It’s the companion to get_query_var(). That’s nice because you can use it outside of pre_get_posts, though you’d have to be careful with that.

    • Brady Vercher

      That’s what I was thinking regarding using it outside of pre_get_posts. I was just told that the theme team recommends using the set() method over the set_query_var() function within pre_get_posts, which makes sense since you have access directly to the object reference. If it’s just to save the overhead of the function call, it seems like the direct assignment method would be preferable.

    • Stephen Harris

      Brady Vercher mentioned set_query_var. You should avoid using this as it applies only the global $wp_query. pre_get_posts is applied to every WP_Query (and sometimes get_posts). So if you only want to alter the main query, ensure to check the is_main_query method. Using $query->set() has the advantage of modifying secondary queries too (if this is desired).

      @Pippin – typo in the code? is_main_query should be $query->is_main_query

    • Pippin

      Yeah, you’ll need to be very careful with set_query_var().

      No, I don’t think that’s incorrect. You only have to use $query->is_main_query when using the pre_get_posts filter. Then action accepts all regular conditional tags. I could be wrong though.

    • Stephen Harris

      So you need to use the $query->is_main_query in pw_filter_query? The method $query->is_main_query checks if $query matches the original query: $wp_the_query. The function is_main_query checks if the global $wp_query matches the original query $wp_the_query. (So, should return false if query_post is used and not reset after wards).

      For instance, in your example with pw_filter_query that should effect all secondary queries on the front page too.

  3. yellowhousedesign

    Hey Pippin,
    Just doing some more testing — running the following: I’m using this to set a front-end URL parameter equal to ‘post_type=THE_POST_TYPE’ and it seems to work for everything but pages. One more interesting aspect is that if you look at the print_r() of $query->query_vars that I have output in that function, you’ll see that when you set the URL paremeter to &post_type=page (like http://localhost/wordpress/?s=test&submit=Search&post_type=page for example) the post_type query variable is missing.

    Right now what I have to do as a workaround is which seems kinda silly – any thoughts? Thanks!

    • Pippin

      That first code is not going to do anything. Your $query->set() function is doing nothing more than setting the post_type var to itself, the existing post type var. You should just use the $_GET method in the second one. No need for the If/ELSE, just use the code in the ELSE.

    • yellowhousedesign

      Appreciate it Pippin – it looks like when I use the following everything works as expected (even pages), but does show the following error when I filter by pages ‘Undefined index: post_type’ which goes back to the original issue – seems kinda weird that when I set it to pages it would show that error. Thanks.

    • Pippin

      I think I know why. You are using add_filter() instead of add_action(). The add_filter() method of using pre_get_posts requires that you return the $query;

  4. yellowhousedesign

    Bleh – think I’m getting the same thing after trying a few variations. If you just so happen want to nail this in the ground, hit me up on skype 🙂

  5. stomp

    Hey Pippin,

    I’ve been trying this technique on my site which uses your Events plugin. Basically in the events archive I just want to show future events.

    I’ve tried this but a little lost now…


    • stomp

      Pippin – Actually don’t waste your time. I should have looked in your code and worked it out from there. Here’s the complete query for anybody who wants run a pre_get_posts meta query.

      It’s for Pippin’s Sugar Events Calendar too 😉

    • Pippin

      Yep that should work just fine 🙂

  6. chrismccoy

    nice tutorial, i did this recently instead of doing custom queries 😉

    • Pippin

      That’s how they should be done 🙂 Thanks Chris!

  7. thrasherstudios77


    Woocommerce Version: 2.0
    Wordpress Version: 3.6

    Having a problem using pre_get_posts and woocommerce hoping someone can help me figure this out. I found this snippet on their front-end snippets page.

    I add this snippet to my functions.php page and tried just the most basic echo statement to check if the hook was working when on the SHOP page.

    function cg_global_settings( $query ) {
    if ( ! is_admin() && is_shop() ) {
    echo ‘This is the Shop page’;
    add_action( ‘pre_get_posts’, ‘cg_global_settings’, 9999 );

    and nothing happens. Now if I use the wp action hook then everything dealing with woocommerce works for me.

    function cg_woocommerce_global_settings() {
    if ( is_shop() ) {
    echo ‘This is the Shop page’;
    if ( is_product_category() ) {
    echo ‘This is a Product Category page’;
    if ( is_product() ) {
    echo ‘This is the Product page’;
    echo var_dump( get_product() );
    add_action( ‘wp’, ‘cg_woocommerce_global_settings’ );

    But I am trying to understand why anything having to do with woocommerce doesn’t seem to work inside the pre_get_posts action hook. I really would like to keep all my custom query filtering inside pre_get_posts action hook.

    What am I missing?

    • Pippin

      I’m not familiar enough with WooCommerce to know that answer to that, sorry.

  8. Robin

    Is there a way to set a fallback orderby with this? I have custom post types with a date field and they need to be sorted by that date. So I’ve got my pre_get_posts set up and working fine, but the site goes into conniptions if someone fails to set that date. They *shouldn’t* fail to set it as it’s kind of critical to the post, but it’s possible. (Or, alternatively, can I force that field to have a value? I’ve taken a step there with adding in today’s date with the default_content hook, but it would still be possible to delete it, and therefore kill things.

    • Pippin

      You should simply do a conditional check on the date before modifying the query. If the date isn’t set, don’t modify the query.

  9. keeks

    Hey Pippin seems like there is something wrong with the encoding of this video. I get audio but no video. I’m using latest Chrome on a Mac.

  10. Mike

    Thanks for the code, I just implemented it, and then noticed it was also modifying my navigation.

    According to the WordPress docs ( simply calling is_main_query() is circular.
    Rather, you should call the passed object’s method directly with $query->is_main_query();

    Doing so fixed my issue

  11. Henry Agami

    this video is black, audio only

  12. Swayam Tejwani

    Hi Pippin, Video is not working for me on Chrome and Mozilla both, it shows error “Error loading this resource”

    • Pippin

      I just tested it and it’s working for me. Where are you based in the world?

Leave a Reply

Error: Please enter a valid email address

Error: Invalid email

Error: Please enter your first name

Error: Please enter your last name

Error: Please enter a username

Error: Please enter a password

Error: Please confirm your password

Error: Password and password confirmation do not match