How To Add Custom WordPress Pagination Without a Plugin

For a WordPress blog, pagination is used to display a limited number of posts per page. Compared to the default WordPress page navigation, numbered pagination allows users the ability to navigate much easier and deeper into the archives, like numbered pages of a book. Pagination is used in almost every Web application to display returned data on multiple pages. More…

A custom numbered pagination will enhance the SEO value of your blog. It enhances users´ experience by allowing them to easily access pages in your archives. It also makes the job easier for search engine robots to crawl your posts especially for complex blogs.

Default WordPress Page Navigation

If you have plenty of posts in your WordPress blog, and depending on your settings, you probably have at the bottom of your home page a « Previous and/or Next » links. These 2 links connects neighboring content and enable users to navigate through other posts.

The following picture shows the default WordPress page navigation:

default WordPress Pagination.

The following picture shows the modified default page navigation that I had in this blog prior to adding the custom numbered pagination code:

Old pagination I had for this blog.

Many people, including myself, are not happy with just using the default WordPress page navigation. It looks ugly, dull and most importantly is not SEO and user friendly. We want a nicer and more appealing custom numbered pagination. This is where this tutorial helps.

Custom WordPress Pagination

The picture below shows the custom numbered pagination generated for this blog.

Miscellaneous examples of numbered pagination for this blog.

If you are wondering how I created 16 pages but I currently have 4, the answer is simple, reduce the number of posts displayed per page in your WordPress dashboard (Settings -> Reading).

Yes, there are plugins for WordPress pagination, such as WP-PageNavi being the most popular and several others. But haven´t you read my previous post about the disadvantages of going crazy adding plugins to your theme? Also what would you do if you want to develop a premium theme with a built-in custom numbered pagination.

WordPress Numbered Pagination Code

This is my version of the numbered pagination code. Open the functions.php file located in your theme´s folder and add (copy and paste) the following two functions. This code is tested to work for the current WordPress version.

CODE-X :: Pagination (Numbered Page Navigation) Code

Note: To scroll within the code: You can also click on the code window and use your keyboard´s arrow keys.

<?php
/*******************************************************************
* @Author: Boutros AbiChedid 
* @Date:   March 20, 2011
* @Websites: http://bacsoftwareconsulting.com/
* http://blueoliveonline.com/
* @Description: Numbered Page Navigation (Pagination) Code.
* @Tested: Up to WordPress version 3.1.2 (also works on WP 3.3.1)
********************************************************************/ 

/* Function that Rounds To The Nearest Value.
   Needed for the pagenavi() function */  
function round_num($num, $to_nearest) {
   /*Round fractions down (http://php.net/manual/en/function.floor.php)*/
   return floor($num/$to_nearest)*$to_nearest;
}

/* Function that performs a Boxed Style Numbered Pagination (also called Page Navigation).
   Function is largely based on Version 2.4 of the WP-PageNavi plugin */   
function pagenavi($before = '', $after = '') {	
	global $wpdb, $wp_query;
	$pagenavi_options = array();
	$pagenavi_options['pages_text'] = ('Page %CURRENT_PAGE% of %TOTAL_PAGES%:');
	$pagenavi_options['current_text'] = '%PAGE_NUMBER%';
	$pagenavi_options['page_text'] = '%PAGE_NUMBER%';
	$pagenavi_options['first_text'] = ('First Page');
	$pagenavi_options['last_text'] = ('Last Page');
	$pagenavi_options['next_text'] = 'Next &raquo;';
	$pagenavi_options['prev_text'] = '&laquo; Previous';
	$pagenavi_options['dotright_text'] = '...';
	$pagenavi_options['dotleft_text'] = '...';
	$pagenavi_options['num_pages'] = 5; //continuous block of page numbers
	$pagenavi_options['always_show'] = 0;
	$pagenavi_options['num_larger_page_numbers'] = 0;
	$pagenavi_options['larger_page_numbers_multiple'] = 5;
	
	//If NOT a single Post is being displayed 
	/*http://codex.wordpress.org/Function_Reference/is_single)*/
	if (!is_single()) {
		$request = $wp_query->request;
		//intval — Get the integer value of a variable
		/*http://php.net/manual/en/function.intval.php*/
		$posts_per_page = intval(get_query_var('posts_per_page'));
		//Retrieve variable in the WP_Query class. 
		/*http://codex.wordpress.org/Function_Reference/get_query_var*/
		$paged = intval(get_query_var('paged'));
		$numposts = $wp_query->found_posts;
		$max_page = $wp_query->max_num_pages;
		
		//empty — Determine whether a variable is empty
		/*http://php.net/manual/en/function.empty.php*/
		if(empty($paged) || $paged == 0) {
			$paged = 1;
		}
		
		$pages_to_show = intval($pagenavi_options['num_pages']);
		$larger_page_to_show = intval($pagenavi_options['num_larger_page_numbers']);
		$larger_page_multiple = intval($pagenavi_options['larger_page_numbers_multiple']);
		$pages_to_show_minus_1 = $pages_to_show - 1;
		$half_page_start = floor($pages_to_show_minus_1/2);
		//ceil — Round fractions up (http://us2.php.net/manual/en/function.ceil.php)
		$half_page_end = ceil($pages_to_show_minus_1/2);
		$start_page = $paged - $half_page_start;
		
		if($start_page <= 0) {
			$start_page = 1;
		}
		
		$end_page = $paged + $half_page_end;
		if(($end_page - $start_page) != $pages_to_show_minus_1) {
			$end_page = $start_page + $pages_to_show_minus_1;
		}
		if($end_page > $max_page) {
			$start_page = $max_page - $pages_to_show_minus_1;
			$end_page = $max_page;
		}
		if($start_page <= 0) {
			$start_page = 1;
		}
		
		$larger_per_page = $larger_page_to_show*$larger_page_multiple;
		//round_num() custom function - Rounds To The Nearest Value.
		$larger_start_page_start = (round_num($start_page, 10) + $larger_page_multiple) - $larger_per_page;
		$larger_start_page_end = round_num($start_page, 10) + $larger_page_multiple;
		$larger_end_page_start = round_num($end_page, 10) + $larger_page_multiple;
		$larger_end_page_end = round_num($end_page, 10) + ($larger_per_page);
		
		if($larger_start_page_end - $larger_page_multiple == $start_page) {
			$larger_start_page_start = $larger_start_page_start - $larger_page_multiple;
			$larger_start_page_end = $larger_start_page_end - $larger_page_multiple;
		}	
		if($larger_start_page_start <= 0) {
			$larger_start_page_start = $larger_page_multiple;
		}
		if($larger_start_page_end > $max_page) {
			$larger_start_page_end = $max_page;
		}
		if($larger_end_page_end > $max_page) {
			$larger_end_page_end = $max_page;
		}
		if($max_page > 1 || intval($pagenavi_options['always_show']) == 1) {
		    /*http://php.net/manual/en/function.str-replace.php */
			/*number_format_i18n(): Converts integer number to format based on locale (wp-includes/functions.php*/
			$pages_text = str_replace("%CURRENT_PAGE%", number_format_i18n($paged), $pagenavi_options['pages_text']);
			$pages_text = str_replace("%TOTAL_PAGES%", number_format_i18n($max_page), $pages_text);
			echo $before.'<div class="pagenavi">'."\n";
			
			if(!empty($pages_text)) {
				echo '<span class="pages">'.$pages_text.'</span>';
			}
			//Displays a link to the previous post which exists in chronological order from the current post. 
			/*http://codex.wordpress.org/Function_Reference/previous_post_link*/
			previous_posts_link($pagenavi_options['prev_text']);
			
			if ($start_page >= 2 && $pages_to_show < $max_page) {
				$first_page_text = str_replace("%TOTAL_PAGES%", number_format_i18n($max_page), $pagenavi_options['first_text']);
				//esc_url(): Encodes < > & " ' (less than, greater than, ampersand, double quote, single quote). 
				/*http://codex.wordpress.org/Data_Validation*/
				//get_pagenum_link():(wp-includes/link-template.php)-Retrieve get links for page numbers.
				echo '<a href="'.esc_url(get_pagenum_link()).'" class="first" title="'.$first_page_text.'">1</a>';
				if(!empty($pagenavi_options['dotleft_text'])) {
					echo '<span class="expand">'.$pagenavi_options['dotleft_text'].'</span>';
				}
			}
			
			if($larger_page_to_show > 0 && $larger_start_page_start > 0 && $larger_start_page_end <= $max_page) {
				for($i = $larger_start_page_start; $i < $larger_start_page_end; $i+=$larger_page_multiple) {
					$page_text = str_replace("%PAGE_NUMBER%", number_format_i18n($i), $pagenavi_options['page_text']);
					echo '<a href="'.esc_url(get_pagenum_link($i)).'" class="single_page" title="'.$page_text.'">'.$page_text.'</a>';
				}
			}
			
			for($i = $start_page; $i  <= $end_page; $i++) {						
				if($i == $paged) {
					$current_page_text = str_replace("%PAGE_NUMBER%", number_format_i18n($i), $pagenavi_options['current_text']);
					echo '<span class="current">'.$current_page_text.'</span>';
				} else {
					$page_text = str_replace("%PAGE_NUMBER%", number_format_i18n($i), $pagenavi_options['page_text']);
					echo '<a href="'.esc_url(get_pagenum_link($i)).'" class="single_page" title="'.$page_text.'">'.$page_text.'</a>';
				}
			}
			
			if ($end_page < $max_page) {
				if(!empty($pagenavi_options['dotright_text'])) {
					echo '<span class="expand">'.$pagenavi_options['dotright_text'].'</span>';
				}
				$last_page_text = str_replace("%TOTAL_PAGES%", number_format_i18n($max_page), $pagenavi_options['last_text']);
				echo '<a href="'.esc_url(get_pagenum_link($max_page)).'" class="last" title="'.$last_page_text.'">'.$max_page.'</a>';
			}
			next_posts_link($pagenavi_options['next_text'], $max_page);
			
			if($larger_page_to_show > 0 && $larger_end_page_start < $max_page) {
				for($i = $larger_end_page_start; $i <= $larger_end_page_end; $i+=$larger_page_multiple) {
					$page_text = str_replace("%PAGE_NUMBER%", number_format_i18n($i), $pagenavi_options['page_text']);
					echo '<a href="'.esc_url(get_pagenum_link($i)).'" class="single_page" title="'.$page_text.'">'.$page_text.'</a>';
				}
			}
			echo '</div>'.$after."\n";
		}
	}
}
?>

After adding the above 2 functions (CODE-X) to your functions.php file and uploaded it to your server, copy and paste the code below in multiple files: index.php, archive.php, search.php, and probably other files located in your theme´s folder. Place the code where you want the page navigation to appear. Make sure to delete the default page navigation code.

CODE-Y :: Adding pagenavi Function

	 <div class="navigation">
        <!-- REMOVE default page navigation once your custom pagination is working. -->
        <?php //REMOVE DEFAULT:  next_posts_link('&laquo;&laquo; Older Posts') 
             //REMOVE DEFAULT:   previous_posts_link('Newer Posts &raquo;&raquo;') ?>
          
        <!-- ADD Custom Numbered Pagination code. -->
		<?php if(function_exists('pagenavi')) { pagenavi(); } ?>
        </div>

Depending on your theme, the code above could be placed in your footer.php file. Frankly, this would be the ideal place, since you will need to add it once.

The Conditional check is to make sure that “pagenavi” function exists, to prevent any erros in case you forgot to add CODE-X first.

Styling the Custom Pagination

Finally we need to style the numbered pagination code with CSS. The following CSS code is what I used to style the pagination for this blog. The code should be added to your theme´s CSS file (usually called style.css). Of course go ahead and change it to fit your design.

CODE-Z :: Styling The Pagination Code

Note: To scroll within the code: You can also click on the code window and use your keyboard´s arrow keys.

/*******************************************************************
* @Author: Boutros AbiChedid 
* @Date:   March 20, 2011
* @Websites: http://bacsoftwareconsulting.com/
* http://blueoliveonline.com/
* @Description: Styling Custom Numbered Page Navigation (Pagination) 
********************************************************************/ 
.pagenavi {
    margin: 0 0 20px 30px;
    padding: 5px 1px 5px;
    float:left;
    width: 98%;
    background:url(images/pagination_bg.gif) no-repeat center;
}	
.pagenavi a {
    padding: 5px 6px 4px 6px; 
    margin: 3px;
    text-decoration: none;
    border: 1px solid #ccc;
    color: #666;
    background-color: inherit;	
}
.pagenavi a:hover {	
    border: 1px solid #444;
    color: #444;
    background-color: #eee;
}
.pagenavi span.pages {
    padding: 5px 6px 4px 6px; 
    margin: 3px;
    color: #825a2d;
    font-weight:bold;
    border: 1px solid #999;
    background-color: inherit;	
}
.pagenavi span.current {
    padding: 5px 6px 4px 6px; 
    margin: 3px;
    font-weight:bold;
    border: 1px solid #666;
    color: #444;
    background-color: #eee;
}
.pagenavi span.expand {
    padding: 5px 6px 4px 6px; 
    margin: 3px;	
    border: 1px solid #ccc;
    color: #444;
    background-color: inherit;	
}
.pagenavi .first, .pagenavi .last {	
    border: 1px solid #aaa;
}
.pagenavi .single_page {	
border: 1px dashed #ccc;	
}

Conclusion

You now have a custom numbered pagination that can be used on your WordPress Website, which is nicer, user friendlier, and has better SEO advantage than the default. If you have any questions or anything else to say, make sure to leave a comment!

Reference

  1. Function Reference « WordPress Codex

If you found this post useful, please consider: linking back to it, subscribing by email to future posts, or subscribing to the RSS feed to have new articles delivered to your feed reader, or feel free to donate. Thanks!

About the Author |
Boutros is a professional Drupal & WordPress developer, Web developer, Web designer, Software Engineer and Blogger. He strives for pixel perfect design, clean robust code, and user-friendly interface. If you have a project in mind and like his work, feel free to contact him. Connect with Boutros on Twitter, and LinkedIn.
Visit Boutros AbiChedid Website.

107 Responses to “How To Add Custom WordPress Pagination Without a Plugin”

  1. Thanks!Worked fine!Instead of your CSS though,I used Bootstrap 2.0,it has some cool pagination CSS.Peace!

  2. Hey Boutros,
    Thanks for a great script. It’s replaced a previously used script in my snippets.

  3. dagash says:

    thanks for this tutorial ,,,
    but i am an arabic , and this is not suitable for us because the direction of numbering .

    i need some modifications to make it as below :

    <> 9 – 8 – 7 -…- 3 – 2 – 1 <>
    i mean every thing should reversed ,,

    • Hi dagash,
      Thanks for your question. Unfortunately, that is something not easy to do (at least I don’t know how to do this).
      I understand your point, in Arabic we read from right to Lett and it will look awkward the way pagination is displayed.
      If you find a solution please share it here.
      “Shoukran”
      Boutros.

      • dagash says:

        again Boutros ,,
        thank you for a quick reply , and ok i will look for this problems and again back here to write about the solution ,,,

        “shoukran” …^_^

  4. jerry says:

    nice tutor and i have been using this a long time; recently i started with custom post type, while the navigation doesn’t work with it, so is there any update for custom post type? thanks again for the great tutor.

  5. Andre says:

    Code works for me in wordpress 3.2.1, thanks!

  6. Nice post.it’s like a lesson for me.

  7. mohammad says:

    good post and good understanding
    again thanks a lot ;)

  8. mohammad says:

    Wow …..my god
    its really nice post.
    Thanks.

  9. utamu says:

    Thanks for the nice guide Boutros
    Can you tell what could be wrong with pagintion in this link http://teknohama.x10.mx/tufanye-nini-blog-yetu/

    • @utamu:

      Hi utamu;
      Checking your link you gave, it seems that the pagination code is not going to page 2 or 3. It gets stuck at page 1. I am not sure?
      When you hover the mouse over page 2 or page3, the URL seems correct.
      1. Do you have any plugins that are interfering with the pagination code. One way to find out for sure is to try to deactivate all plugins and see if the pagination code works.
      2. Another scenario the theme might interfere with the pagination code I wrote. So if you are using a free WordPress theme, give me the link and I can test.
      3. I could not tell What WordPress Version you are using? Version 2.9 and higher should be OK (version 3.0 and above for sure)
      4. Do you really have posts on page 2 and page 3? Even typing the link directly in the address bar does not take you there?
      for example if you type: http://teknohama.x10.mx/tufanye-nini-blog-yetu/page/2/ or http://teknohama.x10.mx/tufanye-nini-blog-yetu/page/3/ the browser will always render the first page!!!!

      I am not sure what’s going on there in step 4, this is where I start debugging. I think your problem lies in number 4.
      Hope this helps.

  10. utamu says:

    Thanks for guides and functions .
    But can u help on what cuould be wrong with pagintion with this custom page http://teknohama.x10.mx/tufanye-nini-blog-yetu/

  11. Philip says:

    I like this function and it works perfect in my home made template/theme.
    So my next step is to rework the css code to style the output on my pages as I want it.
    There is one thing I can’t figure out.
    It has to to with next_text and prev_text. I want to style the output of this function differently than the other output. However next_text and prev_text doesn’t seem to have a separate css class. Can you point me in the right direction on how to add a separate class.

    • Hi Philip,

      Sorry for the delay as I was on vacation and I am trying to catch up on things.

      ‘previous’ and ‘next’ are set on lines 29 and 28 of the code. You can set a separate CSS class for them and style them separately. And that’s how it is done:
      For the ‘previous’, replace line 29 of the code:

      $pagenavi_options['prev_text'] = '« Previous';

      with the following line:

      $pagenavi_options['prev_text']='<span class="prev">« Previous</span>';

      And you do the same for the ‘next’ like so:

      Replace line 28 of the code:

      $pagenavi_options['next_text'] = 'Next »';

      with the following line:

      $pagenavi_options['next_text']='<span class="next">Next »</span>';

      And now in the CSS file (style.css):
      Style the .next and .prev classes as you wish, for instance here are the classes for this blog:

      .next {
      font-weight:bold;
      color: #5A7EBC;
      background-color: inherit;
      }
      .prev {
      font-weight:bold;
      color: #5A7EBC;
      background-color: inherit;
      }
      

      Hope this helps.
      Boutros.