Pagination For Custom Post Type In WordPress

Quick Summary

  • Pass get_query_var('paged') into your custom post type WP_Query and build the page links from that query's max_num_pages.
  • Sorting by the press_publish_date meta field leaves out posts without that field, and the previous and next icons need Font Awesome.
  • On a static front page, WordPress stores the page number in the page query var instead of paged.

When you run your own WP_Query for a custom post type in a page template, WordPress’s standard pagination functions still work with the main query, so they don’t know about yours. You need to pass the current page number to your query and build the page links from that query’s max_num_pages. This example lists posts of a press post type, ten per page, sorted by a date field. Both parts go in the same theme template file.

The query and the loop

PHP
<?php
$paged = (get_query_var('paged')) ? get_query_var('paged') : 1;

$data = new WP_Query(array(
    'post_type' => 'press', // your post type name
    'posts_per_page' => 10,
    'post_status' => 'publish',
    //'orderby' => 'menu_order',
    'meta_key' => 'press_publish_date',
    'orderby' => 'meta_value',
    'order' => 'DESC',
    'ignore_sticky_posts' => true,
    'paged' => $paged,
)); ?>

<?php

if ($data->have_posts()):
    while ($data->have_posts()): $data->the_post(); ?>

<?php get_template_part('template-part/press-block'); ?>

<?php endwhile; ?>
  • get_query_var('paged') reads the page number from the URL and falls back to 1. The result is passed to the paged argument.
  • Change post_type and posts_per_page to suit your site.
  • meta_key with 'orderby' => 'meta_value' sorts by the press_publish_date field, newest first. An ACF date picker saves dates as Ymd, so this text sort keeps them in date order. Posts without the field are left out; setting a default date on the ACF date picker fills it in on new posts.
  • get_template_part() loads template-part/press-block.php from your theme for each post.
PHP
<div class = "news-list-pagination" >

<?php

$total_pages = $data->max_num_pages;

if ($total_pages > 1) {

    $current_page = max(1, get_query_var('paged'));

    echo paginate_links(array(
        'base' => get_pagenum_link(1).
        '%_%',
        //'format' => '/page/%#%',
        'current' => $current_page,
        'total' => $total_pages,
        'prev_text' => __('<i class="fas fa-chevron-left page-prev"></i>'),
        'next_text' => __('<i class="fas fa-chevron-right page-next"></i>'),
    ));
} ?>

</div>

<?php
else : ?>
    <h3 > <?php _e('404 Error: Not Found', ''); ?> </h3> <?php endif; ?>
<?php wp_reset_postdata(); ?>
  • $data->max_num_pages is the total number of pages, and the links only print when there is more than one.
  • paginate_links() builds the numbered links from the URL of page one. To reuse it across templates, wrap it in a function as in adding pagination to a custom WP_Query in your theme. The previous and next labels are Font Awesome icons, so Font Awesome must be loaded on your site.
  • When the query finds nothing, the else branch prints a not found heading. wp_reset_postdata() then restores the main query’s post data.

On a static front page

If the template is used as a static front page, WordPress stores the page number in the page query var instead of paged.

Share on X (opens in a new tab) Share on LinkedIn (opens in a new tab) Share on Facebook (opens in a new tab)

ChatGPT (opens in a new tab) Claude (opens in a new tab) Gemini (opens in a new tab) Perplexity (opens in a new tab) Grok (opens in a new tab)

More Insights

See More