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.

* @Author: Boutros AbiChedid 
* @Date:   March 20, 2011
* @Websites:
* @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 (*/
   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 
	if (!is_single()) {
		$request = $wp_query->request;
		//intval — Get the integer value of a variable
		$posts_per_page = intval(get_query_var('posts_per_page'));
		//Retrieve variable in the WP_Query class. 
		$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
		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 (
		$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) {
		    /* */
			/*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. 
			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). 
				//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(); } ?>

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:
* @Description: Styling Custom Numbered Page Navigation (Pagination) 
.pagenavi {
    margin: 0 0 20px 30px;
    padding: 5px 1px 5px;
    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;
    border: 1px solid #999;
    background-color: inherit;	
.pagenavi span.current {
    padding: 5px 6px 4px 6px; 
    margin: 3px;
    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;	


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!


  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. naresh says:

    category is repeating in pagination

    I am using this code to break category in pagination and showing 5-5 chunks in pagination. I have 50 categories in a WordPress site. I wants to show each five categories on one page and next 5 on next page and next 5 on next page and so on with pagination.
    Why this code is repeating category in pagination.

        	  'ignore_sticky_posts' =&gt; true
        	$incats = new WP_Query($args);
        	// Minimal Loop; proof of concept only
        	if ($incats-&gt;have_posts()) {
        	  while ($incats-&gt;have_posts()) {
        		echo '';
        	// paginate
        	$page_args = array(
        		'base'         =&gt; '%_%',
        		'format'       =&gt; '?page=%#%',
        		'total'        =&gt; count($cats),
        		'current'      =&gt; $page,
        		'show_all'     =&gt; False,
        		'end_size'     =&gt; 1,
        		'mid_size'     =&gt; 2,
        		'prev_next'    =&gt; True,
        		'prev_text'    =&gt; __('« Previous'),
        		'next_text'    =&gt; __('Next »'),
        		'type'         =&gt; 'plain',
        	echo paginate_links($page_args);
  2. Parvez says:

    Nice information, thanks man, i am looking for this.

  3. Rashed says:

    Hi !
    I Developed the custom post type wp plugin but I can’t add pagination. How to add the pagination? here ismy script. Please let me know how to solve this.


    ‘Portfolio’,'posts_per_page’ => 4)); ?>
    ‘Portfolio’, ‘posts_per_page’ => 4 ); $loop = new WP_Query( $mypost ); ?>
    have_posts() ) : $loop->the_post(); {?>

  4. Is there a way to put a style class for the Previous and next class?

    • Yes.
      On lines 28 and 29 of the code you replace them with the following:

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

      Notice that I wrapped them with ‘next’ and ‘previous’ classes that you can define in your theme’s ‘style.css’ file.
      Hope this helps.

  5. Erik v/d Ven says:

    AWESOME!!! This really helped me out!! I know the wp-pagenavi plugin uses the same functionality but that doesn’t work with custom SQL queries.

    This does, just had to manually fill in some variables and it works great!

  6. maj says:

    Thank you for this, it works perfectly. However, When there’s not enough posts to spread through out 5 pages (say there’s only 2 posts and they will fit in one page), pagination will still show 1 – 5 pages. Any idea why this is happening.
    Thanks again.

    • It shouldn’t do that. I think that have to do with your theme. There a plugin conflict, somewhere? or some built in code in your theme?
      I suggest that you temporarily switch to a different theme and try my pagination code and see if you still get the same behavior.

  7. George says:

    Hi. Thanks for this. At first I was having trouble until I really looked at your function. The global declaration of $wp_query wasn’t relevant to how I structured by custom post loops. So, I just passed my query variable to your function and it worked like a charm!!!

  8. MatC says:

    Thank you for this great pagination, I use it on my new blog!

  9. jiweb says:

    Hi,love this solution for pagnitation,however,i used this code:
    Posted by Furqan on: April 11, 2012 at 12:08 PM
    and this is my page code:
    everything shows up ok,but when i click on 2,3,or 4 it redirects me to my first page instead of next post page.I have costum template and i use /%postname%/ as permalink, i know there is something wrong with my query_posts,but i dont have any idea how to fix it . Any ideas whats wrong with my code?

  10. adri says:


    Does this code work on the adventure-journal 1.0 theme?

    I have added your code to a different theme with no problem, but when I add it to this theme it doesn’t work.

    Thank you.

  11. Lisa says:

    Hi, I tried this code and I can see it on my page, but when I click on the second or third page, for example, it doesn’t work. How can I resolve that? Sorry for my bad english :)

    • Hi Lisa,

      what WordPress theme are you using (link for the theme?) And show me an example (another link).
      I can’t tell from your generic problem.

      • Lisa says:

        Hi, thanks a lot for your help. I am creating a custom theme, but I have a problem with wordpress too, when I create a news page and I pubblish it, the page in front end is pubblished, but in back end I see a white page, maybe it’s a error of wordpress instalaltion. I have already wp installed and i created a new folder inside (my site is and i created a forder called lisa and a new installation of wp inside, maybe this is a problem), however…..I installed wordpress_it_IT_3_3_2 into lisa folder and you can see there the problem with the page navigation. I haven’t installed plugin in lisa yet, so i cannot have conflict.
        Look here
        Thanks a lot for your help