0

I am having trouble getting pagination working for custom taxonomy archive with the custom query. The pagination on the taxonomy archive ends up with a 404 page.

I have to run a query multiple times on many areas, so I have created a method for that.

Query Method

/**
 * @param bool  $post_type
 * @param bool  $posts_per_page
 * @param bool  $paged
 * @param array $extra_args
 *
 * @return \WP_Query
 */
public function module_query( $post_type = FALSE, $posts_per_page = FALSE, $paged = FALSE, array $extra_args = [] ) {
    // basic arguments
    $args = [
        'post_type'      => $post_type ? $post_type : self::get_module_cpt(),
        'posts_per_page' => $posts_per_page ? $posts_per_page : - 1,
        'orderby'        => 'title',
        'order'          => 'ASC',
    ];

    // set paged
    if ( $paged ) {
        $args[ 'paged' ] = $paged;
    }

    $args = wp_parse_args( $extra_args, $args );

    // init meta query var
    $meta_query   = [];
    $meta_query[] = [
        'relation' => 'AND',
        [
            'key'     => 'permit_roles',
            'compare' => 'NOT EXISTS',
        ],
        [
            'key'     => 'permit_users',
            'compare' => 'NOT EXISTS',
        ],
    ];
    $meta_query[] = [
        'relation' => 'AND',
        [
            'key'     => 'permit_roles',
            'value'   => '',
            'compare' => '=',
        ],
        [
            'key'     => 'permit_users',
            'value'   => '',
            'compare' => '=',
        ],
    ];

    // init permit roles var
    $permit_roles = [];
    $r            = 0;

    // loop through each roles and prepare array for meta query
    foreach ( $this->get_current_user_roles() as $current_user_role ) {
        $permit_roles[ $r ][ 'key' ]     = 'permit_roles';
        $permit_roles[ $r ][ 'value' ]   = sprintf( ':"%s";', $current_user_role );
        $permit_roles[ $r ][ 'compare' ] = 'LIKE';

        $r ++;
    }

    $meta_query[] = [
        'relation' => 'OR',
        $permit_roles,
        [
            'key'     => 'permit_users',
            'value'   => sprintf( ':"%s";', get_current_user_id() ),
            'compare' => 'LIKE',
        ],
    ];

    // add meta query relation if more than one query
    if ( count( $meta_query ) > 1 ) {
        $meta_query[ 'relation' ] = 'OR';
    }

    // add meta query only for non admin users
    if ( ! current_user_can( 'administrator' ) ) {
        $args[ 'meta_query' ] = $meta_query;
    }

    // return the query object
    return new WP_Query( $args );
}

taxonomy.php Query

$paged = ( get_query_var( 'paged' ) ) ? get_query_var( 'paged' ) : 1;

$the_query = $cp->module_query(
    $cp::get_resource_cpt(), cp_get_addons_per_page(), $paged,
    [
        'orderby'   => 'publish_date',
        'tax_query' => [
            'taxonomy' => $term->taxonomy,
            'field'    => 'slug',
            'terms'    => [ $term->slug ],
            'operator' => 'IN',
        ],
    ]
);

...
// while loop
...

// pagination
$cp->bootstrap_pagination( $the_query );

// reset query
wp_reset_postdata();

Pagination function

The pagination is working fine on the Custom Post time page with a custom loop.

function bootstrap_pagination( \WP_Query $wp_query = NULL, $echo = TRUE ) {

    if ( NULL === $wp_query ) {
        global $wp_query;
    }

    $pages = paginate_links( [
            'base'         => str_replace( 999999999, '%#%', esc_url( get_pagenum_link( 999999999 ) ) ),
            'format'       => '?paged=%#%',
            'current'      => max( 1, get_query_var( 'paged' ) ),
            'total'        => $wp_query->max_num_pages,
            'type'         => 'array',
            'show_all'     => FALSE,
            'end_size'     => 3,
            'mid_size'     => 1,
            'prev_next'    => TRUE,
            'prev_text'    => __( '« Prev' ),
            'next_text'    => __( 'Next »' ),
            'add_args'     => FALSE,
            'add_fragment' => '',
        ]
    );

    if ( is_array( $pages ) ) {

        $pagination = '<div class="row"><div class="col mt-5"><div class="pagination d-flex justify-content-center"><ul class="pagination pagination-sm">';

        foreach ( $pages as $page ) {
            $pagination .= '<li class="page-item' . ( strpos( $page, 'current' ) !== FALSE ? ' active' : '' ) . '"> ' . str_replace( 'page-numbers', 'page-link', $page ) . '</li>';
        }

        $pagination .= '</ul></div></div></div><!-- end pagination -->';

        if ( $echo ) {
            echo $pagination;
        } else {
            return $pagination;
        }
    }

    return NULL;
}
  • If you must paginate a secondary loop/query in an archive page, then you can use page_num, pg, etc. query string in the pagination URL. But paged is reserved for the main query and it would result in the 404 error when the value is greater than the max number of pages for the main query. – Sally CJ Jul 6 at 7:12
  • @SallyCJ I understand a bit. but not sure how and where exactly to run the second loop and what I should pass? – pixelngrain Jul 6 at 7:25
  • 1
    I hope the answer helps? :) – Sally CJ Jul 6 at 9:03
1

In short, your code is fine, but the custom query's pagination and the one for the main query, they can't be using the same paged URL query string (which WordPress reads the value from for the paged query arg used in a WP_Query request).

Why the error 404?

So for examples, paginated/paged category requests can have one of these URLs:

  • Full pretty URL:
    https://example.com/category/uncategorized/page/2/

  • Semi-pretty URL which redirects to the above URL:
    https://example.com/category/uncategorized/?paged=2

  • "Ugly URL" (e.g. when permalinks are not enabled)
    https://example.com/?cat=1&paged=2

And as you can see, the URLs are using the query string paged which you can get the value using get_query_var( 'paged' ). (Note that in the first URL, the page/2 is equivalent to paged=2)

And because a category/taxonomy archive is a non-singular request (just like search results pages), the paged value will be used in the main query's request, i.e. the SQL command for querying the requested archive, unless of course if pagination is not enabled for the main query.

Therefore, the paged value needs to be valid, i.e. it must not exceed the max number of pages for that specific request (the main query). And if the max is exceeded, then you'd get a 404 error because there were no more posts found via the main query. Just like a book with 10 pages (including covers); there's logically no page #11, right?

So because your custom query is being paginated using the paged query string, then if you get the error 404, it's likely because the custom query had more pages than the main query.

Sample scenario demonstrating the above:

  • On a category archive page (e.g. at example.com/category/uncategorized), there were 3 pages shown in the pagination. And that's for the main query which WordPress runs first on page load.

  • Then in the category template, you run a custom query for, maybe a custom post type.

  • Then you added a pagination for the custom query and there were 5 pages shown in that pagination.

  • So if both the paginations are using the same paged query string, then for example, going to page #4 will cause a 404 error because the main query (for the category) had only at most 3 pages of results.

How to fix the error

(Note: These are listed in no specific order.)

  • As I said in the comments, you can use a custom URL query string like page_num, pg, etc. along with paged.

    So the paged will always be just for the main query, while page_num or pg will be used with your custom query.

  • Use AJAX instead to paginate the custom query, but AJAX is not in scope of this answer. (Or that it's up to you to look for a solution and implement it.)

  • Use a static Page (post of the page type), e.g. at example.com/my-tax-archive, and run your custom queries in the Page Template.

    For singular requests, WordPress doesn't use the paged in the request SQL, so the paged value can be any number (2, 20, 200, 2000, etc.).

But there's a trick to make paged works for both the main query and custom queries.. on archive pages.

  1. Add this to your theme's functions.php file:

    add_action( 'pre_get_posts', function ( $query ) {
        if ( is_admin() || ! $query->is_main_query() ||
            ! is_tax( 'your_tax' ) ) {
            return;
        }
    
        if ( ! empty( $_GET['pgs'] ) &&
            ( $paged = max( 1, $query->get( 'paged' ) ) ) &&
            // Runs only if the current page number exceeds the main query's max pages.
            $paged > $_GET['pgs']
        ) {
            $query->set( 'pg', $paged );          // for the custom query's pagination
            $query->set( 'paged', $_GET['pgs'] ); // for the main query's pagination
    
            // Prevent WordPress from redirecting to page/<max pages>.
            remove_action( 'template_redirect', 'redirect_canonical' );
        } else {
            $query->set( 'pg', $query->get( 'paged' ) );
        }
    } );
    
  2. Then in the bootstrap_pagination() function, apply the four ([1] to [4]) changes below:

    // [1] Add the $use_alt
    function bootstrap_pagination( \WP_Query $wp_query = NULL, $echo = TRUE, $use_alt = null ) {
    
        ...
    
        // [2] Add these:
        $add_args = [];
        if ( $use_alt ) {
            $add_args['pgs'] = $GLOBALS['wp_query']->max_num_pages;
        }
    
        $pages = paginate_links( [
                ...
                // [3] Use this instead.
                'current'      => max( 1, get_query_var( $use_alt ? 'pg' : 'paged' ) ),
                ...
                // [4] Use the $add_args
                'add_args'     => $add_args,
                ...
            ]
        );
    
        ...
    }
    
  3. Then in the archive/taxonomy.php template:

    // Define the $paged like so:
    $paged = max( 1, get_query_var( 'pg' ) );
    
    // ... your code here.
    
    // Then call bootstrap_pagination() like so:
    bootstrap_pagination( $the_query, true, true );
    

Tried & tested working, but obviously as you can see above, once the max num pages for the main query has been reached, the next pages will set the paged to that max num pages value. So it's up to you how to not make that "look bad/weird/whatever" and to handle things like performance (if your custom query had 20 extra pages than the main query, then one would see the main query's last page 20 times.. if they viewed them all).

| improve this answer | |
  • Thank you so much @SallyCJ. I appreciate your detailed explanation. However, I am still unable to resolve the issue. The query I am using, it's a method I have posted in the question. I can't see anything wrong but if you can find anything please let me know. – pixelngrain Jul 6 at 9:46
  • Is it just page #2 and above that gives you the error 404? And although I'm not positive about this, perhaps flushing the rewrite rule helps? (Just visit the permalink settings page) But if not, can you add your taxonomy and post type registration code? – Sally CJ Jul 6 at 10:55
  • Hi, please ignore my previous comment and see the revised answer. :) – Sally CJ Jul 7 at 3:19
  • I truly appreciate your effort and dedication to helping me. I have tried this but it is not working and end up with 404. I have no idea what is wrong here. I have to flush the permalink as well. It is generating a URL like this. http://site.dev/taxonomy/term/page/2/?pgs=1 – pixelngrain Jul 7 at 11:04
  • 1
    It was my stupid mistake. I overlooked is_tax() argument. I have many taxonomies that run the same loop so instead of passing taxonomy in is_tax() I leave it blank so that will apply to all taxonomies. Here is the video loom.com/share/15214f95ac794fda9638345f46ecb0ba Millions thanks agin. – pixelngrain Jul 7 at 12:25

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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