I have added a custom/secondary query to a template file/custom page template; how can I make WordPress use my custom query for pagination, instead of using the main query loop’s pagination?
Addendum
I have modified the main loop query via query_posts()
. Why isn’t pagination working, and how do I fix it?
The Problem
By default, in any given context, WordPress uses the main query to determine pagination. The main query object is stored in the
$wp_query
global, which is also used to output the main query loop:When you use a custom query, you create an entirely separate query object:
And that query is output via an entirely separate loop:
But pagination template tags, including
previous_posts_link()
,next_posts_link()
,posts_nav_link()
, andpaginate_links()
, base their output on the main query object,$wp_query
. That main query may or may not be paginated. If the current context is a custom page template, for example, the main$wp_query
object will consist of only a single post – that of the ID of the page to which the custom page template is assigned.If the current context is an archive index of some sort, the main
$wp_query
may consist of enough posts to cause pagination, which leads to the next part of the problem: for the main$wp_query
object, WordPress will pass apaged
parameter to the query, based on thepaged
URL query variable. When the query is fetched, thatpaged
parameter will be used to determine which set of paginated posts to return. If a displayed pagination link is clicked, and the next page loaded, your custom query won’t have any way to know that the pagination has changed.The Solution
Passing Correct Paged Parameter to the Custom Query
Assuming that the custom query uses an args array:
You will need to pass the correct
paged
parameter to the array. You can do so by fetching the URL query variable used to determine the current page, viaget_query_var()
:You can then append that parameter to your custom query args array:
Note: If your page is a static front page, be sure to use
page
instead ofpaged
as a static front page usespage
and notpaged
. This is what you should have for a static front pageNow, when the custom query is fetched, the correct set of paginated posts will be returned.
Using Custom Query Object for Pagination Functions
In order for pagination functions to yield the correct output – i.e. previous/next/page links relative to the custom query – WordPress needs to be forced to recognize the custom query. This requires a bit of a “hack”: replacing the main
$wp_query
object with the custom query object,$custom_query
:Hack the main query object
$temp_query = $wp_query
$wp_query = NULL;
Swap the custom query into the main query object:
$wp_query = $custom_query;
This “hack” must be done before calling any pagination functions
Reset the main query object
Once pagination functions have been output, reset the main query object:
Pagination Function Fixes
The
previous_posts_link()
function will work normally, regardless of pagination. It merely determines the current page, and then outputs the link forpage - 1
. However, a fix is required fornext_posts_link()
to output properly. This is becausenext_posts_link()
uses themax_num_pages
parameter:As with other query parameters, by default the function will use
max_num_pages
for the main$wp_query
object. In order to forcenext_posts_link()
to account for the$custom_query
object, you will need to pass themax_num_pages
to the function. You can fetch this value from the$custom_query
object:$custom_query->max_num_pages
:Putting it all together
The following is a basic construct of a custom query loop with properly functioning pagination functions:
Addendum: What About
query_posts()
?query_posts()
for Secondary LoopsIf you’re using
query_posts()
to output a custom loop, rather then instantiating a separate object for the custom query viaWP_Query()
, then you’re_doing_it_wrong()
, and will run into several problems (not the least of which will be pagination issues). The first step to resolving those issues will be to convert the improper use ofquery_posts()
to a properWP_Query()
call.Using
query_posts()
to Modify the Main LoopIf you merely want to modify the parameters for the main loop query – such as changing the posts per page, or excluding a category – you may be tempted to use
query_posts()
. But you still shouldn’t. When you usequery_posts()
, you force WordPress to replace the main query object. (WordPress actually makes a second query, and overwrites$wp_query
.) The problem, though, is that it does this replacement too late in the process to update the pagination.The solution is to filter the main query before posts are fetched, via the
pre_get_posts
hook.Instead of adding this to the category template file (
category.php
):Add the following to
functions.php
:Instead of adding this to the blog posts index template file (
home.php
):Add the following to
functions.php
:That way, WordPress will use the already-modified
$wp_query
object when determining pagination, with no template modification required.When to use what function
Research this question and answer and this question and answer to understand how and when to use
WP_Query
,pre_get_posts
, andquery_posts()
.I use this code for custom loop with pagination:
Source:
Awesome as always Chip. As an addendum to this, consider the situation whereby you are using a global page template attached to a Page for some “intro text” and it’s followed by a subquery that you want to be paged.
Using paginate_links() as you mention above, with mostly defaults, (and assuming you have pretty permalinks turned on) your pagination links will default to
mysite.ca/page-slug/page/#
which is lovely but will throw404
errors because WordPress doesn’t know about that particular URL structure and will actually look for a child page of “page” that’s a child of “page-slug”.The trick here is to insert a nifty rewrite rule that only applies to that particular “pseudo archive page” page slug that accepts the
/page/#/
structure and rewrites it to a query string that WordPress CAN understand, namelymysite.ca/?pagename=page-slug&paged=#
. Notepagename
andpaged
notname
andpage
(which caused me literally HOURS of grief, motivating this answer here!).Here’s the redirect rule:
As always, when changing rewrite rules, remember to flush your permalinks by visiting Settings > Permalinks in the Admin back-end.
If you have multiple pages that are going to behave this way (for example, when dealing with multiple custom post types), you might want to avoid creating a new rewrite rule for each page slug. We can write a more generic regular expression that works for any page slug you identify.
One approach is below:
Disadvantages / Caveats
One disadvantage of this approach that makes me puke in my mouth a little is the hard-coding of the Page slug. If an admin ever changes the page slug of that pseudo-archive page, you’re toast – the rewrite rule will no longer match and you’ll get the dreaded 404.
I’m not sure I can think of a workaround for this method, but it would be nice if it were the global page template that somehow triggered the rewrite rule. Some day I may revisit this answer if no one else has cracked that particular nut.
Great answer Chip created needs to be modified today.
For some time we have
$wp_the_query
variable that should be equal to the$wp_query
global just after the main query executes.This is why this the part from the Chip’s answer:
is not needed anymore.
We can forget this part with creating the temporary variable.
So now we can call:
or even better we can call:
Everything other Chip outlined stays.
After that query-reset-part you can call the pagination functions that are
f($wp_query)
, â they depend on$wp_query
global.In order to further improve the pagination mechanics and to give more freedom to the
query_posts
function I created this possible improvement:https://core.trac.wordpress.org/ticket/39483