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”.
1 2 3 4 5 6 7 8 9 10 11 | 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); |
Join Now for $6
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!
You mean sort options for setting the order items are displayed? Or what they are sorted by?
See Paul’s comment below. Is that what you’re referring to?
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!
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
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
https://gist.github.com/2995754
I really don’t know. As far as I know, they do exactly the same thing.
Exploring Core a bit I think I’ve found the answer.
$query->set() is defined as this:
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:
So no, there is really no difference at all.
Aside from the two methods mentioned, there’s actually a third function to do the same thing: set_query_var( $var, $value ).
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.
That’s what I was thinking regarding using it outside of pre_get_posts. I was just told that the WP.com 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.
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 everyWP_Query
(and sometimesget_posts
). So if you only want to alter the main query, ensure to check theis_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
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.
So you need to use the
$query->is_main_query
inpw_filter_query
? The method$query->is_main_query
checks if$query
matches the original query:$wp_the_query
. The functionis_main_query
checks if the global$wp_query
matches the original query$wp_the_query
. (So, should return false ifquery_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.Hey Pippin,
Just doing some more testing — running the following: http://pastebin.com/k6sLishJ 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 http://pastebin.com/c1mUNHKu which seems kinda silly – any thoughts? Thanks!
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.
Appreciate it Pippin – it looks like when I use the following http://pastebin.com/U1Rjs36X 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.
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;
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 🙂
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…
https://gist.github.com/3018263
Cheers,
Steve
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.
https://gist.github.com/3018263
It’s for Pippin’s Sugar Events Calendar too 😉
Yep that should work just fine 🙂
nice tutorial, i did this recently instead of doing custom queries 😉
That’s how they should be done 🙂 Thanks Chris!
Hi
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.
http://docs.woothemes.com/document/exclude-a-category-from-the-shop-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?
I’m not familiar enough with WooCommerce to know that answer to that, sorry.
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.
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.
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.
Thanks for the code, I just implemented it, and then noticed it was also modifying my navigation.
According to the WordPress docs (http://codex.wordpress.org/Function_Reference/is_main_query) 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
Hi,
this video is black, audio only
Thanks
Henry
Hi Pippin, Video is not working for me on Chrome and Mozilla both, it shows error “Error loading this resource”
I just tested it and it’s working for me. Where are you based in the world?