The root problem here is the call to query_posts
:
$args = array('post_type' => array('posts', 'affiliate-products'));
query_posts($args);
At no point do you tell it which page to fetch, so it always fetches the first page, aka the default.
You could add this, but fundamentally, the entire approach is incorrect, and has many other problems you might not be aware of. For example, you don’t call wp_reset_query
, so any loops or code that run after your post loop will be broken. query_posts
usage is a massive red flag, avoid it at all costs.
Sadly, a lot of legacy code still exists, and a lot of very old tutorials that mistakenly teach this to new people.
The Real Way To Change What Posts Get Shown
If you want to change which posts appear on a page, you’re supposed to use the pre_get_posts
filter so that it fetches what you want the first time.
Otherwise, you’re throwing away the main query after the fact, then making a second query, making the page as much as 2x slower. Then, because you created your own query, none of the built in functionality is present, so you have to rebuild that too.
Instead, in your functions.php
hook into pre_get_posts
like this:
add_action( 'pre_get_posts', function( \WP_Query $query) {
//
} );
Now you can use $query->set
and $query->get
to change the arguments before it goes to the database. You can use all the conditionals too such as $query->is_home()
or $query->is_main_query()
.
For example, this filter will set the search page to only show 5 posts:
add_action( 'pre_get_posts', function( \WP_Query $query) {
if ( $query->is_main_query() && $query->is_search() ) {
$query->set( 'posts_per_page', 5 );
}
} );
Now the search listings show 5 posts per page, pagination still works, it’s just as fast, no performance penalty, and other features still work too.
Some notes:
pre_get_posts
is a filter that modifies a standard main post loop. Use it with a normal loop, template modification is not necessary, if you’re modifying templates to implement this then something has gone wrong- Never ever under any circumstances use
query_posts
. There is no valid use case that isn’t covered by better/easier options. Erase this from your mind and if you come across it treat it with deep suspicion, it’s a major red flag - If you need to change the posts the main loop fetches, use the
pre_get_posts
filter - If you need to do a second query, use
WP_Query
, but never use it to change or replace the main query. This is more for things like sidebar post lists, etc - Remember to cleanup after yourself, when using
WP_Query
you should callwp_reset_postdata
afterwards or you’ll break things - Don’t smush all your loop statements together on one line, it makes things harder to read and confuses editors and tools
For reference, this is what a standard post loop looks like:
if ( have_posts() ) {
while ( have_posts() ) {
the_post();
// display the post
}
} else {
// nothing was found
}
If you need to put a query_posts
call before it, then something has gone horribly wrong. Use pre_get_posts
instead.