How to add proper pagination as default in WordPress themes

When you scroll to the bottom of an index page on a WordPress site, what do you see? Chances are it’s two links, one saying “Older Posts” and one saying “Newer Posts”. Scarcely has any default setting been more engagingly uninformative, and unnecessarily so. This “let’s not tell the visitor where she is on the site nor how much other content is available” approach to navigation is the epitome of User Experience archaism and should be but a footnote in the annals of WordPress history. Instead it is a cornerstone of the free WordPress theme experience and the default setting for all baseline WordPress themes (_s included) with one groundbreaking exception: Twenty Fourteen.

It is time we the people who build WordPress themes cast aside the rotting corpse of Older and Newer posts and replace it with proper pagination. After all the function is already built into WordPress – in fact it has been for a very, very long time. So, without further ado let me provide you with the code you need to get started and a few tweaks to move your WordPress pagination into the 21st century.

paginate_links() – the old friend you never knew you had

You don’t need fancy plugins or custom code to get proper pagination in your index pages. The paginate_links() function will do that for you in a snap. Here it is in its most basic form, straight from the Codex:


<?php
global $wp_query;

$big = 999999999; // need an unlikely integer

echo paginate_links( array(
	'base' => str_replace( $big, '%#%', esc_url( get_pagenum_link( $big ) ) ),
	'format' => '?paged=%#%',
	'current' => max( 1, get_query_var('paged') ),
	'total' => $wp_query->max_num_pages
) );
?>

(If you’re wondering what the $big variable is for it helps output a clean URL when the page is loaded. Pay no mind.)

Slot this code into your template file where you want the pagination to appear (i.e. replace the current Older Posts and Newer Posts code with this one) and you’re good to go. If you want the basic solution that is. Which probably isn’t what you want.

Options Aplenty

Luckily for us we don’t have to be content with the default example above. paginate_links() has plenty of parameters we can use to change its output. The parameters you should care about from a design perspective are:

  • end_size: Decides how many numbers to display at the start and end of the list. Defaults to 1 meaning you’ll get output like this: 2 … 5 6 7 … 10. If you change the number to 2 you’ll get 2 3 … 5 6 7 … 9 10. You get the idea. Visually 1 looks best but in some cases you may want to change this value.
  • mid_size: Decides how many numbers to display on the left and right side of the current page (not including the current page). Defaults to 1. So if you set mid_size to 2 and are on page 6 you’d get 2 … 4 5 6 7 8 … 10.
  • prev_next: True or false decides whether a Previous and Next link should appear in the list or not. The actual wording is controlled separately (see below).
  • prev_text and next_text: The actual text output displayed if prev_next is set to true. Default output is « Previous and Next » and the strings are wrapped and prepared for translation.
  • type: Decides what the function outputs. The default plain outputs a list of anchor tags (links) which imo makes no sense. This can be switched to either array (great if you want complete control and know how to handle arrays in PHP) or list which outputs an unordered list and should be the default setting. I strongly recommend  setting type to list so you can work with the output in a reasonable way.

There are other variables but they are technical and control the functionality which I’ll address at the end of this article.

Upcycling existing code

Proper WordPress Pagination
So, let’s say you want to add pagination to your site (not unlike what you see directly above here) and you want to do it in a clean way. In that case it behooves you to create a function, stash it in functions.php or template-tags.php in your theme or child theme, and call it in when necessary. The easiest way to get this working, and working properly, is to upcycle the function provided in Twenty Fourteen and make some subtle changes to its output:


<?php

if ( ! function_exists( 'yourtheme_paging_nav' ) ) :
/**
 * Display navigation to next/previous set of posts when applicable.
 * Based on paging nav function from Twenty Fourteen
 */

function yourtheme_paging_nav() {
	// Don't print empty markup if there's only one page.
	if ( $GLOBALS['wp_query']->max_num_pages < 2 ) {
		return;
	}

	$paged        = get_query_var( 'paged' ) ? intval( get_query_var( 'paged' ) ) : 1;
	$pagenum_link = html_entity_decode( get_pagenum_link() );
	$query_args   = array();
	$url_parts    = explode( '?', $pagenum_link );

	if ( isset( $url_parts[1] ) ) {
		wp_parse_str( $url_parts[1], $query_args );
	}

	$pagenum_link = remove_query_arg( array_keys( $query_args ), $pagenum_link );
	$pagenum_link = trailingslashit( $pagenum_link ) . '%_%';

	$format  = $GLOBALS['wp_rewrite']->using_index_permalinks() && ! strpos( $pagenum_link, 'index.php' ) ? 'index.php/' : '';
	$format .= $GLOBALS['wp_rewrite']->using_permalinks() ? user_trailingslashit( 'page/%#%', 'paged' ) : '?paged=%#%';

	// Set up paginated links.
	$links = paginate_links( array(
		'base'     => $pagenum_link,
		'format'   => $format,
		'total'    => $GLOBALS['wp_query']->max_num_pages,
		'current'  => $paged,
		'mid_size' => 3,
		'add_args' => array_map( 'urlencode', $query_args ),
		'prev_text' => __( '&larr; Previous', 'yourtheme' ),
		'next_text' => __( 'Next &rarr;', 'yourtheme' ),
		'type'      => 'list',
	) );

	if ( $links ) :

	?>
	<nav class="navigation paging-navigation" role="navigation">
		<h1 class="screen-reader-text"><?php _e( 'Posts navigation', 'yourtheme' ); ?></h1>
			<?php echo $links; ?>
	</nav><!-- .navigation -->
	<?php
	endif;
}
endif;

?>

To make this work in a template simply call the function like this:


<?php yourtheme_paging_nav(); ?>

The function will output clean HTML markup with the pagination links wrapped in list items within an unordered list like this:


<nav class="navigation paging-navigation" role="navigation">
	<h1 class="screen-reader-text">Posts navigation</h1>
	<ul class='page-numbers'>
		<li><a class="prev page-numbers" href="http://localhost/page/7/">&larr; Previous</a></li>
		<li><a class='page-numbers' href='http://localhost/'>1</a></li>
		<li><span class="page-numbers dots">&hellip;</span></li>
		<li><a class='page-numbers' href='http://localhost/page/5/'>5</a></li>
		<li><a class='page-numbers' href='http://localhost/page/6/'>6</a></li>
		<li><a class='page-numbers' href='http://localhost/page/7/'>7</a></li>
		<li><span class='page-numbers current'>8</span></li>
		<li><a class='page-numbers' href='http://localhost/page/9/'>9</a></li>
		<li><a class='page-numbers' href='http://localhost/page/10/'>10</a></li>
		<li><a class='page-numbers' href='http://localhost/page/11/'>11</a></li>
		<li><span class="page-numbers dots">&hellip;</span></li>
		<li><a class='page-numbers' href='http://localhost/page/14/'>14</a></li>
		<li><a class="next page-numbers" href="http://localhost/page/9/">Next &rarr;</a></li>
	</ul>
</nav><!-- .navigation -->

Now all you need is some basic CSS to make it all look good:


/* =Index pagination
----------------------------------------------- */

/* The containing box with a nice white background */
.paging-navigation {
	font-family: sans-serif;
	padding: 1em;
	background: #fff;
	background: hsl(0, 0%, 100%);
}

/* Remove bullets and list indentation */
.paging-navigation ul {
	list-style-type: none;
	margin: 0;
	padding: 0;
}

/* Make the list items appear horizontally */
.paging-navigation li {
	display: inline;
}

/* Give each link and the current item some padding to make them easy to click */
a.page-numbers,
span.page-numbers {
	padding: .3em .7em;
	color: #333;
	color: hsl(0, 0%, 20%);
}

/* Link hover state */
a:hover.page-numbers {
	color: #000;
	color: hsl(0, 0%, 0%);
}

/* Current page bold and dark */
.paging-navigation .current {
	font-weight: bold;
	color: #000;
	color: hsl(0, 0%, 0%);
}

Of course you can elaborate on this to your heart’s content, but this is the gist of it.

Go Forth and Paginate

Now that you see how easy it is to add proper pagination to your WordPress index pages I think you will agree with me there is no good reason to keep the old Older and Newer posts links in place. If you don’t agree with me, by all means leave your disagreements in the comments below and let’s have a discussion. Otherwise, familiarize yourself with paginate_links(); and give your visitors a better user experience.

24 thoughts on “How to add proper pagination as default in WordPress themes

  1. Hi Morten,
    In the section “To make this work in a template simply call the function like this:” you’ve displayed the html output of the function, not the function call.

    Nice article though – I totally agree the default WordPress pagination is so confusing.

  2. nice tutorial but what if we want it for posts etc ,,,as above code is for pages what if we want it in post pagination

  3. I am having a weird problem with pagination. I have it on my site to show the name of the next and previous posts, just like you do – but I have a weird color block that I cannot get rid of.
    This is the code I am using:
    function next_previous_navigation_links() {

    if ( is_single ( ) ) { ;?>

    <?php
    } }
    add_action('genesis_before_comment_form', 'next_previous_navigation_links', 20 );

    How can I get rid of that little block of color? Example post: http://icouldgiveafork.com/garden-2014-update-may-23/

    For the next post – which does not exist – it shows a square of color. It does this at the last one too when there aren't any previous posts.

    Any help would be great! I know your post is about adding pagination a different way – so can I use it but have the post names like you do instead of numbers?

  4. Hi Morten

    I think you should add the rel=”prev” and rel=”next” attribute to the links for Google. You can do this simply by a string replace.

    Benjamin

  5. Hi, mor10
    it that possible to append our class to that and element intead of <ul class="page-numbers", how can do that?

  6. Hi there,

    looks like a great tut. However, I am having trouble paginating on a custom template where my query has to use $wpdb->get_results(“SELECT {$wpdb->prefix}posts.* FROM {$wpdb->prefix}posts, {$wpdb->prefix}postmeta WHERE {//custom conditions} LIMIT {$offset}, {$ppp}”);
    Any ideas how to tweek the function to work with a custom query?

  7. For any one who may have had my same question, the solution is easy.
    Just pass a $max_num_pages variable through, and change all the two $GLOBALS['wp-query']->max_numpages to $max_num_pages and you are good to go.

    Hope this helps

  8. mor10 –
    have you run into issues with pagination links for categories when using custom permalinks. basically /page/2 etc not working and sending you to 404?

    I have a custom structure of /garden-help/%category%/%postname%/ and pagination links created by paginate_links() return 404s.

    It looks like this might be a long time bug, but this old plugin still seems to be needed and still fixes the issue: https://wordpress.org/plugins/category-pagination-fix

  9. For some reason this will not work for the website I am making… I have no idea why. I am using the exact same code (based off of the Simone theme, with tweaks to make the site what I needed). The blog page shows the pagination at the bottom, and when I click on “2” it goes to “http://www.jmsmkt.net/countryside/about/blog/page/2/” but it is just the first page reloaded, not the next set of posts. I have already changed the permalinks to default and it didn’t change anything, and I deactivated all plugins to test and it didn’t fix anything.
    Any thoughts?

  10. Dear Sir, I do i call above mentioned pagination in custom post type in page template ?

  11. Thank you for this, I found it really useful and well detailed. I was looking to simply change the text for the ‘older posts’ links but this is so much better.

  12. Simple and works, had to read alot of articles with vage explanations and code that does not work. Thanks alot for this simple solution!

  13. Hello.

    I used this for my wordpress site and it worked great however I just want to max the number of page boxes that appear in the navigation to 5 so…

    1 … 5 6 … 10.

    Can anyone help in where I write the info to max the page numbers in the function page?

    Thanks
    Ed

  14. Hello Morten,

    At last it worked 🙂 , thnx for this great article, all worked fine from the first time i tested , been looking to create a good navigation for the last 5 days 🙂

    thnx again

Comments are closed.