I read @nacin’s You don’t know Query yesterday and was sent down a bit of a querying rabbit hole. Before yesterday, I was (wrongly) using query_posts()
for all my querying needs. Now I’m a little bit wiser about using WP_Query()
, but still have some gray areas.
What I think I know for sure:
If I’m making additional loops anywhere on a pageâin the sidebar, in a footer, any kind of “related posts”, etcâI want to be using WP_Query()
. I can use that repeatedly on a single page without any harm. (right?).
What I don’t know for sure
- When do I use @nacin’s
pre_get_posts
vs.WP_Query()
? Should I usepre_get_posts
for everything now? - When I want to modify the loop in a template page â lets say I want to modify a taxonomy archive page â do I remove the
if have_posts : while have_posts : the_post
part and write my ownWP_Query()
? Or do I modify the output usingpre_get_posts
in my functions.php file?
tl;dr
The tl;dr rules I’d like to draw from this are:
- Never use
query_posts
anymore - When running multiple queries on a single page, use
WP_Query()
- When modifying a loop, do this __________________.
Thanks for any wisdom
Terry
ps: I have seen and read: When should you use WP_Query vs query_posts() vs get_posts()? Which adds another dimension â get_posts
. But doesn’t deal with pre_get_posts
at all.
You are right to say:
pre_get_posts
pre_get_posts
is a filter, for altering any query. It is most often used to alter only the ‘main query’:(I would also check that
is_admin()
returns false – though this may be redundant.). The main query appears in your templates as:If you ever feel the need to edit this loop – use
pre_get_posts
. i.e. If you are tempted to usequery_posts()
– usepre_get_posts
instead.WP_Query
The main query is an important instance of a
WP_Query object
. WordPress uses it to decide which template to use, for example, and any arguments passed into the url (e.g. pagination) are all channelled into that instance of theWP_Query
object.For secondary loops (e.g. in side-bars, or ‘related posts’ lists) you’ll want to create your own separate instance of the
WP_Query
object. E.g.Notice
wp_reset_postdata();
– this is because the secondary loop will override the global$post
variable which identifies the ‘current post’. This essentially resets that to the$post
we are on.get_posts()
This is essentially a wrapper for a separate instance of a
WP_Query
object. This returns an array of post objects. The methods used in the loop above are no longer available to you. This isn’t a ‘Loop’, simply an array of post object.In response to your questions
pre_get_posts
to alter your main query. Use a separateWP_Query
object (method 2) for secondary loops in the template pages.pre_get_posts
.There are two different contexts for loops:
Problem with
query_posts()
is that it is secondary loop that tries to be main one and fails miserably. Thus forget it exists.To modify main loop
query_posts()
pre_get_posts
filter with$query->is_main_query()
checkrequest
filter (a little too rough so above is better)To run secondary loop
Use
new WP_Query
orget_posts()
which are pretty much interchangeable (latter is thin wrapper for former).To cleanup
Use
wp_reset_query()
if you usedquery_posts()
or messed with global$wp_query
directly – so you will almost never need to.Use
wp_reset_postdata()
if you usedthe_post()
orsetup_postdata()
or messed with global$post
and need to restore initial state of post-related things.There are legitimate scenarios for using
query_posts($query)
, for example:You want to display a list of posts or custom-post-type posts on a page (using a page template)
You want to make pagination of those posts work
Now why would you want to display it on a page instead of using an archive template?
It’s more intuitive for an administrator (your customer?) – they can see the page in the ‘Pages’
It’s better for adding it to menus (without the page, they’d have to add the url directly)
If you want to display additional content (text, post thumbnail, or any custom meta content) on the template, you can easily get it from the page (and it all makes more sense for the customer too). See if you used an archive template, you’d either need to hardcode the additional content or use for example theme/plugin options (which makes it less intuitive for the customer)
Here’s a simplified example code (which would be on your page template – e.g. page-page-of-posts.php):
Now, to be perfectly clear, we could avoid using
query_posts()
here too and useWP_Query
instead – like so:But, why would we do that when we have such a nice little function available for it?
I modify WordPress query from functions.php:
Just to outline some improvements to the accepted answer since WordPress evolved over the time and some things are different now (five years later):
Actually is an action hook. Not a filter, and it will affect any query.
Actually, this is also not true. The function
have_posts
iterates theglobal $wp_query
object that is not related only to the main query.global $wp_query;
may be altered with the secondary queries also.Actually, nowadays
WP_Query
is a class, so we have an instance of a class.To conclude: At the time @StephenHarris wrote most likely all this was true, but over the time things in WordPress have been changed.