search ]

Add Pagination to a WordPress Theme Without a Plugin

Want to replace the default “Next Posts” and “Previous Posts” links with numbered pagination? While plugins like WP-PageNavi can do this, adding pagination manually keeps your theme lightweight and avoids unnecessary CSS and JavaScript files.

WordPress includes a built-in function called paginate_links() that creates numbered pagination for any query in your theme.

By default, the function comes with the following settings:

<?php $args = array(
 'base'               => '%_%',
 'format'             => '?page=%#%', // the '%#%' will be replaced with the page number
 'total'              => 1,
 'current'            => 0,
 'show_all'           => False,
 'end_size'           => 1,
 'mid_size'           => 2,
 'prev_next'          => True,
 'prev_text'          => __('« Previous'),
 'next_text'          => __('Next »'),
 'type'               => 'plain',
 'add_args'           => False,
 'add_fragment'       => '',
 'before_page_number' => '',
 'after_page_number'  => ''
); ?>

For a full list of parameters, see the official documentation. Below is a step-by-step guide to adding numbered pagination to your WordPress theme.

It’s worth mentioning that you can forgo traditional pagination and add the infinite scroll option or alternatively, the option to load more posts via a button.

Modern alternative: Since WordPress 4.1, you can use the_posts_pagination() for the main query. It generates accessible pagination markup out of the box. The manual approach below gives you more control over custom queries and styling.

Adding Pagination to a WordPress Theme

Add the following code to your theme’s functions.php file (or any other file you find appropriate in your theme):

/**
 * Pagination for archive, taxonomy, category, tag and search results pages
 *
 * @global $wp_query https://developer.wordpress.org/reference/classes/wp_query/
 * @return Prints the HTML for the pagination if a template is $paged
 */
function sv_pagination() {
	global $wp_query;

    $next_arrow = is_rtl() ? esc_html( '<' ) : esc_html( '>' );
    $prev_arrow = is_rtl() ? esc_html( '>' ) : esc_html( '<' );

	$total      = $wp_query->max_num_pages;

	$big = 999999999; // This needs to be an unlikely integer

	// For more options and info view the docs for paginate_links()
	// https://developer.wordpress.org/reference/functions/paginate_links/
	$paginate_links = paginate_links( array(
		'base'        => str_replace( $big, '%#%', esc_url( get_pagenum_link($big) ) ),
		'current'     => max( 1, get_query_var('paged') ),
		'total'       => $total,
		'show_all'    => true,
		'prev_text'	  => $prev_arrow,
		'next_text'	  => $next_arrow
	) );

	// Display the pagination if more than one page is found
	if ( $paginate_links ) {
		echo '<div class="pagination">';
		echo $paginate_links;
		echo '</div>';
	}
}

We’ve set the basic parameters for paginate_links(). To make the pagination accessible (and you should), add the following variable at the beginning of the function:

$translated = __( 'Page', 'mytextdomain' );

Then, add the following parameter to the paginate_links array in the above code:

'before_page_number' => '<span class="accessible-txt">'.$translated.' </span>'

You might want to hide the accessible-txt class in your CSS file…

Styling the Pagination

We’ll use Flexbox to make things easier. To style the numbered pagination like this blog, add the following code to your child theme’s style.css file:

.pagination {
    display: flex;
    justify-content: space-between;
    flex-flow: row wrap;
}

.pagination > * {
    color: #2d2d2d;
    border-radius: 50%;
    text-align: center;
    flex-basis: 36px;
    line-height: 36px;
    min-width: 36px;
    margin: 4px 0;
    background: #f3f3ef;
}

.pagination > a:hover,
.pagination span.current {
    background-color: #942762;
    color: #fefefe;
}

Adding Pagination to Your Theme

It’s very simple. To call the pagination function in your theme, just add the following line of code where you want the pagination to appear. Typically, you’ll add this code to index.php, category.php, archive.php, and similar files, but if you have a custom page template, add it there:

<?php sv_pagination(); ?>

If you’re using a template file or page that already has the default WordPress pagination, replace it with this code.

Custom Query Loop?

If you are using a custom loop with WP_Query, the code won’t work unless you assign your query to the $wp_query variable (don’t do this). To fix this, when I create a custom loop, it usually looks like this:

$sv_query = new WP_Query( $args );

Then modify the pagination function to detect your custom query and adjust $total accordingly:

global $wp_query, $sv_query;
if ( $sv_query ) {
    $total = $sv_query->max_num_pages;
} 
else {
    $total = $wp_query->max_num_pages;
}

Your custom loop should basically look like the following:

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

    $args     = array(
        'post_type'      => 'post', // You can add a custom post type if you like
        'posts_per_page' => 6,
        'paged'          => $paged
    );
    $sv_query = new WP_Query( $args );

    if ( $sv_query->have_posts() ) : while ( $sv_query->have_posts() ) : $sv_query->the_post();

        //*** Post content goes here 

    endwhile;
        sv_pagination();
        wp_reset_postdata();
    else :

        //*** If no posts found message goes here 

    endif;
?>

Note that after the loop ends, we call wp_reset_postdata() to restore the global $post variable. This is essential when using custom WP_Query loops.

FAQs

What is the difference between paginate_links() and the_posts_pagination()?
the_posts_pagination() is a higher-level function introduced in WordPress 4.1 that generates accessible pagination markup for the main query with minimal configuration. paginate_links() is a lower-level function that gives you full control over the output and works with custom WP_Query loops. Use the_posts_pagination() for simple cases and paginate_links() when you need custom styling or custom queries.
Why is my pagination not working with WP_Query?
Custom WP_Query loops need the 'paged' parameter set correctly. Make sure you pass get_query_var('paged') to your query arguments and that your pagination function uses the custom query's max_num_pages instead of the global $wp_query. Also, remember to call wp_reset_postdata() after your loop.
How do I make WordPress pagination accessible?
Use the before_page_number parameter in paginate_links() to add screen-reader-friendly text like "Page" before each number. Wrap the pagination in a nav element with an aria-label. The aria_current parameter (default 'page') automatically marks the current page for assistive technologies.
Should I use a plugin for pagination or add it manually?
For most cases, manual pagination using paginate_links() or the_posts_pagination() is preferred. It avoids loading extra CSS and JavaScript files, gives you full control over the markup and styling, and keeps your theme lightweight. Plugins like WP-PageNavi are useful if you need a quick solution without writing code.
Does pagination affect SEO?
Yes. Numbered pagination helps search engines discover and crawl all your content. It also improves user experience, which is an indirect ranking factor. For SEO best practices, ensure each paginated page has a unique canonical URL and consider using self-referencing canonical tags on each page.

Summary

Numbered pagination provides a clear way for users to navigate your blog and helps search engines discover all your content. With just a few lines of PHP and CSS, you can add custom pagination to any WordPress theme.

For SEO considerations around paginated pages, check out our guide on pagination and the rel=next and rel=prev tags.

Join the Discussion
0 Comments  ]

Leave a Comment

To add code, use the buttons below. For instance, click the PHP button to insert PHP code within the shortcode. If you notice any typos, please let us know!

Savvy WordPress Development official logo