I got a paged loop that pulls in 4 posts per page from custom post type. It works great, with the only downside that the pagination wont go over the page/20 everything after that is 404, and the posts_nav_link won't show the next arrow at all. Like the pages just aren't there.

In total there are 119 published posts in the custom post type. If I change posts_per_page to, say 6, then I get access to more of the posts from the end, but it also stops at the page/20.

On the production site the pagination stops at 21, I guess because the live database has more entries.

I got the "Blog pages show at most" setting set to 6.

Here's the loop I'm using:

$paged = get_query_var('paged') ? get_query_var('paged') : 1;
$args  = array(
    'posts_per_page' => 4,
    'orderby'        => 'modified',
    'post_type'      => "projekte",
    'paged'          => $paged
$custom_query = new WP_Query($args);
$c = 0;
while($custom_query->have_posts()) :
wp_reset_postdata(); // reset the query

I've gone through the functions.php a million times looking for a some limiting get_pre_posts filter but there's nothing I can see.

I also got an error log setup and nothing is returned there, I've tested it that it works.

Any suggestions?

  • 1
    Do a test, add 'suppress_filters' => true, to your arguments. This will inhibit all filters from changing your query. Just for interest sake, in which template are you doing this Jan 28, 2015 at 18:37
  • Thanks for the tip, unfortunately it didn't help, but it's great that it rules the filters out. It's in archive-projekte.php projekte being the cpt.
    – any_h
    Jan 28, 2015 at 20:01

1 Answer 1


What you are experiencing is quite normal and expected. This is one of the big reasons I always hammer on this point, never use custom queries to replace the main query on the home page or any type of archive page. They solve one issue but creates plenty other new ones

Lets look at what you have and why you are getting these results:


Although you don't see the output on your archive page, the main query executes on every page load and returns an array of posts regardless. Removing the loop does not stop the main query from being executed. The loop just displays what is being retrieved by the main query. So what is basically happening, you are retrieving two sets of posts per page load if you replace the default loop with a custom one. The posts retrieved by the main query and posts retrieved by the custom query. If you want to test this, add the following line of code outside the loop on your archive page within php tags

?><pre><?php var_dump( $wp_query->posts ); ?></pre><?php

This will output the array of posts retrieved by the main query on that specific page

This makes custom queries very ineffecient when is replaces the main query. It is like replacing an inflated tyre which is in tip top shape with a punctured, worn tyre.


You have the following scenario

  • 119 posts from your custom post type

  • Posts per page option set in admin is 6

  • Custom query is set to retrieve 4 posts per page


Your main query will return 20 pages which you can test with echo $wp_query->max_num_pages;. The maths is easy, you have 119 posts, which you can also check with echo $wp_query->found_posts;, posts per page is set to 6 in admin, so the ceiling of 119 / 6 = 20

As for the custom query, you can access the same info as above by changing $wp_query to $custom_query. You will see that you still have 119 posts, but you will have 30 pages due to the fact that posts per page is now set to 4. The ceiling for 119 / 4 = 30

404 ON PAGE 21

Whenever there are no posts to display by the main query, a 404 is triggered, and this is what you are seeing. There are enough posts to fill 20 pages, not 21 or more. So when go to page 21, the main query 404's simply because there are no more posts to display regardless if you still have plenty to display from your custom query.

I have seen posts where people say the solution is easy, simply change the amount of posts per page in the admin page to match that of your custom query. Don't ever do that. Yes, it does fix the issue, but it defeats the real issue here that you should not replace the main query with a custom one. And BTW, the real solution to the problem is pretty simple, clean and is the proper correct method to achieve what you need


You have decided to go with a custom query because of two things

  • Different amount of posts per page on your custom post type archive page that the rest of the site

  • Have posts sorted by date modified and not the default post date

As you can see, you have solved two issues with your custom query but have created other problems


This the correct, simple method to solve your issue. Use pre_get_posts to alter the main query before it is executed. pre_get_posts alters the query variables accordingly which is used to calculated the SQL query for the specific page request.

You have to do two things here, the first would be to remove your custom query on your archive page and replace it with the default loop. Once you have done that, you will see 6 posts per page ordered by post date as it should be by default.

Now, open functions.php and use pre_get_posts to correct the amount of posts per page and sort order

add_action( 'pre_get_posts', function ( $q ) 
    if(    !is_admin() 
        && $q->is_main_query() 
        && $q->is_post_type_archive( 'projekte' ) 
    ) {
        $q->set( 'posts_per_page', 4          );
        $q->set( 'orderby',        'modified' );

You should now see 4 posts per page ordered by modified date, correctly paged on your custom post type archive page projekte


You should also read the following post I had done on a similar issue a while ago, and all the posts I have linked to in that specific post


For those stuck with the dinosaur php versions which does not support closures, here is the fall back version of the pre_get_posts action

add_action( 'pre_get_posts', 'wpse176347_pre_get_posts' );
function wpse176347_pre_get_posts( $q ) 
    if(    !is_admin() 
        && $q->is_main_query() 
        && $q->is_post_type_archive( 'projekte' ) 
    ) {
        $q->set( 'posts_per_page', 4          );
        $q->set( 'orderby',        'modified' );
  • Epic answer :) I've known of pre_get_posts post but not to this depth. Thanks for explaining! The anonymous function is a nice touch also.
    – any_h
    Jan 29, 2015 at 14:34
  • Glad my answer helped. Enjoy :-) Jan 29, 2015 at 14:40
  • The server seems to run a really old PHP, so the anonymous functions gives an error. It's an easy fix though.
    – any_h
    Jan 29, 2015 at 15:31
  • Strange that some servers still uses php versions prior to 5.4. Support for 5.3 has recently been dropped, so servers still using 5.2 (which you are using) and 5.3 poses security risks Jan 29, 2015 at 15:54
  • Totally agree, for this project it's out of my hands :(
    – any_h
    Jan 29, 2015 at 17:58

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.