How To Add Pagination To Your WordPress Theme

Quick Summary

  • Add numbered pagination to a custom WP_Query with three pieces: the query, markup below the loop and a paginate_links helper in functions.php.
  • The bfaf_theme_pagination helper reads $bf_af_query as a global, falls back to $wp_query, and formats links to match your permalink settings.
  • The query takes the page number from get_query_var('paged'), and it belongs in the page template, not a template part, so the helper can read it as a global.

WordPress’s built-in pagination functions work with the main query, so when you list posts with your own WP_Query you need to add pagination yourself. These three pieces add numbered pagination to a page that lists a custom post type (before-after here): the query, the markup where the links appear, and a helper function in functions.php that prints them with paginate_links().

1. The query on the page

PHP
<?php
$paged = (get_query_var('paged')) ? get_query_var('paged') : 1;
$args = array(
    'post_type' => 'before-after',
    'posts_per_page' => 3,
    'paged' => $paged
);
$bf_af_query = new WP_Query($args);
?>
<?php if ($bf_af_query->have_posts()): ?>
<?php while ($bf_af_query->have_posts()):
$bf_af_query->the_post(); ?>
<a id="bf-af-card" href="<?php echo the_permalink(); ?>">
<h4><?php the_title(); ?></h4>
</a>
<?php endwhile;
wp_reset_postdata(); ?>
<?php else:  ?>
<p><?php _e( 'Sorry, no posts matched your criteria.' ); ?></p>
<?php endif; ?>
  • Change post_type and posts_per_page to suit your content.
  • The first line reads the current page number from get_query_var( 'paged' ), falling back to 1, and 'paged' => $paged tells the query which page to load. Paginating a custom post type query uses the same line.
  • Keep this code in the main page template, not in a template part. The pagination function reads $bf_af_query as a global variable, which only works when the query is created in the global scope.

2. The pagination markup

Place this block below the loop in the same template. The wrapper uses Bootstrap grid classes (container, row, col-12); swap them for your theme’s own classes if you don’t use Bootstrap.

PHP
<div class="pagination">
    <div class="container">
        <div class="row">
            <div class="col-12 col-lg-12 col-xl-12">
            <!-- Pagination Support -->
            <?php bfaf_theme_pagination(); ?>
            <!-- Pagination Support -->
            </div>
        </div>
    </div>
</div>

3. The function in functions.php

PHP
// Pagination Start
// Numbered Pagination
if ( !function_exists( 'bfaf_theme_pagination' ) ) {
	
	function bfaf_theme_pagination( ) {
		
		$prev_arrow = is_rtl() ? '→' : '←';
		$next_arrow = is_rtl() ? '←' : '→';
		
		global $wp_query, $bf_af_query;
		$total = $wp_query->max_num_pages;
		$big = 999999999;

		//before after query
		if ( $bf_af_query ) {
			$total = $bf_af_query->max_num_pages;
		} else {
			$total = $wp_query->max_num_pages;
		}
		
		if( $total > 1 )  {
			 if( !$current_page = get_query_var('paged') )
				 $current_page = 1;
			 if( get_option('permalink_structure') ) {
				 $format = 'page/%#%/';
			 } else {
				 $format = '&paged=%#%';
			 }
			echo paginate_links(array(
				'base'			=> str_replace( $big, '%#%', esc_url( get_pagenum_link( $big ) ) ),
				'format'		=> $format,
				'current'		=> max( 1, get_query_var('paged') ),
				'total' 		=> $total,
				'mid_size'		=> 3,
				'type' 			=> 'list',
				'prev_text'		=> $prev_arrow,
				'next_text'		=> $next_arrow,
			 ) );
		}
	}
	
}
  • The function_exists() check lets a child theme override bfaf_theme_pagination().
  • It uses the page count of $bf_af_query when that query exists and falls back to the main $wp_query otherwise, so the same function also works on regular archives.
  • Nothing is printed when there is only one page.
  • The link format follows your permalink settings: page/2/ with pretty permalinks, &paged=2 without.
  • 'type' => 'list' outputs the links as a <ul> list, mid_size sets how many page numbers appear on each side of the current one, and is_rtl() flips the arrows on right-to-left sites.

For more background, see WPExplorer’s guide to pagination in WordPress themes.

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