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

    Hi, Boutros. Thanks for the random guidance! What do I need to do to get this to work in ‘content-single.php’? I know that the method of pagination for single articles is different, but I would love to implement this style of pagination throughout my single articles. Any advice? With the code from your post it returns the proper count of articles(posts), but it tries to navigate through the current post as if it has that many pages. I’d like really like to get this to work for single articles of a specific category.

    • Thanks Dylan for your comment. I am not sure I understood what your trying to do.
      Are you trying to split the post into several pages? If that’s the case then you can use:
      <!--nextpage--> tag in your post’s content.
      See this reference for more details: http://en.support.wordpress.com/splitting-content/nextpage/

      Hope this helps.
      Boutros.

      • Dylan says:

        Thanks for the reply, Boutros. No, I’m not trying to split up my post into several pages, in fact, it’s what I’m trying to avoid.

        What I’m trying to do is this:

        when a user selects a post from a list and is presented with the post in single format, I’d like for the post pagination to carry-over into this experience too. So when when a user is finished reading the article they may navigate the articles.

        Please see my question on stack overflow for more details :)

        http://stackoverflow.com/questions/11286174/wordpress-pagination-for-single-php

        Any help would be awesome.

        • Hi Dylan,

          I got your point but I am not sure if (or how) this could be implemented. Sorry!
          Please keep me updated once you have a solution. I would like to see the code and the end result on the browser side.
          Thanks.

        • Dylan says:

          I’m onto something. There’s a class in the generic twetyeleven theme’s functions.php around line 441:

          if ( ! function_exists( 'twentyeleven_content_nav' ) ) :
           /**
            * Display navigation to next/previous pages when applicable
            */
           function twentyeleven_content_nav( $nav_id ) {
               global $wp_query;
           
               if ( $wp_query->max_num_pages > 1 ) : ?>
                  <nav id="">
                       
                       <?php next_posts_link( __( '← Older posts', 'twentyeleven' ) ); ?>
                       <?php previous_posts_link( __( 'Newer posts →', 'twentyeleven' ) ); ?>
                  <!-- #nav-above -->
              <?php endif;
          }
          endif; // twentyeleven_content_nav
          
          /**
           * Return the URL for the first link found in the post content.
           *
           * @since Twenty Eleven 1.0
           * @return string|bool URL or false when no link is present.
           */
          function twentyeleven_url_grabber() {
              if ( ! preg_match( '/<a>]*?href=[\'"](.+?)[\'"]/is', get_the_content(), $matches ) )
                  return false;
          
              return esc_url_raw( $matches[1] );
          }
          </a>
          

          I’m trying to figure out how to tweak your code to be used in this class. Maybe you could look into it too?

  2. Satrap says:

    Hi Boutros,

    Thank you so much for providing this code.
    I am in the process of getting rid of as many plugins as I can and I have spend the last 3 days trying to figure out how to do the pagination thing manual.

    Now, thanks to your code, I got it working. However, I am still stuck with CSS part of it.

    My theme’s style.css file has only the author information in it. It however has 5 other CSS files. one is called Print.CSS and the other 4 are for each color (themes comes with 4 diff colors) option (like red.css, blue.css).

    Now, I have tried putting the css code in every one of those files, but it still doesn’t work. Any ideas as to why it may be?

    By the way, where and how exactly would you paste the css code. I mean, do you just add it at the end of the file or…?

    Thanks in advance.

    • Hi Satrap,

      Add the code in the “style.css” file. Yes, add it at the end of the CSS file but it does not matter unless you have some conflicting classes.
      If that does not work, let me know what WordPress theme you are using (LINK? assuming it’s free) and I can take a look.
      Thanks.

      • Satrap says:

        Hi Boutros,

        Thank you very much for your reply. I know you are busy like all everyone else. So it is Much appreciated.

        I did try adding it to the style.css, but it just didn’t work.

        The theme is called “Technoholic”. Here is the direct link: http://ericulous.com/2008/05/28/wp-theme-technoholic-free-and-premium/

        Thank you in advance.

        • Hi Satrap,

          I tried the Technoholic theme on my Blog.
          Styling for the pagination code for my blog. I added it to the ‘style.css’ file (see file below).
          If it is not working for your case, the only thing I could think of is that you have a plugin that is interfering (do you have a pagination plugin?)
          OR when you added the CSS code, you probably added it within the comments section.
          In any case below is the ‘style.css’ file and the resulting image.
          Technoholic CSS file
          Technoholic Pagination image

          Boutros.

        • Satrap says:

          Hi Boutros,

          Sorry I had to reply to my own last comment since there was no option to reply to your last comment.

          Anyway, yeah, I tried it again and it didn’t work. I do not have any pagination plugin installed. But, you are right, it probably is a plugin that is interfering.

          I’ll try to one by one disable each and see whcih one is the problem.

          In any case, thank you very much for taking time to respond to my questions. I really apprciate it. All the best to you.

  3. [...] looking to integrate pagination into your next design without needing a plugin, definitely check out Boutros AbiChedid’s article on how this can be achieved. [...]

  4. fanani says:

    Thanks man…
    I’m in the middle developing wordpress theme and want to add page navigation without plugin in there. So, your code really help me.
    thanks

  5. starfall says:

    You are totally a great help for other users of wordpress. I really appreciate your idea. Great job admin. Maybe for some who doesn’t want simple css style. I’ll share this to you all. Hope you enjoy your pagination.

    .wp-pagenavi {
    padding : 10px 20px 10px;
    display : block;
    clear : both;
    }
    .wp-pagenavi a, .wp-pagenavi span.pages, .wp-pagenavi span.extend {
    color : #707070;
    background : #fff;
    border-radius : 3px;
    border : #dcdcdc solid 1px;
    padding : 6px 9px 6px 9px;
    margin-right : 3px;
    text-decoration : none;
    font-size : 12px;
    }
    .wp-pagenavi a:hover {
    color : #fff;
    border-color : #478223;
    background : #599f2f;
    }
    .wp-pagenavi span.current {
    padding : 6px 9px 6px 9px;
    border : #dcdcdc solid 1px;
    border-color : #3390ca;
    border-radius : 3px;
    color : #fff;
    margin-right : 3px;
    border-color : #478223;
    background : #599f2f;
    }
    .wp-pagenavi {
    font-size : 12px;
    }
    

    Simple yet attractive. Hope to read more of your tutorials.

  6. Richard@CameraBooru says:

    Hey,

    I just found your web site and I just wanted to say thanks for the hack. I’ve already implemented it on my site and I’m planning to include it in a theme I’m about to release.

    By the way, smart move, including all of the author/credit info along with your PHP and CSS. I wish more developers were as diligent in taking credit for free hacks they share on the web.

  7. Luis says:

    thanks for the nice work it works great in normal loop.

    now i’ve created a page template with a custom loop for a custom post type
    but the pagination shows the number of pages of the ‘normal’ loop.

    this is my custom loop


    'video', 'posts_per_page' => 10, 'paged' => $paged );
    $loop = new wp_query( $args );
    while ( $loop->have_posts() ) : $loop->the_post(); ?>

    tnks
    Luis Mrk

  8. [...] Boutros AbiChedid wird eine Möglichkeit gezeigt, wie die WordPress Pagination erweitert werden kann. Und das [...]

  9. Sonny says:

    thanks a lot dude.

  10. Naziman says:

    At first, i search about to change previous and next post to post title in post. But i found this. Its good, i want try this in my blog. Right now i’m using plugin for pagination.

    Besides, how to change the default previous and next post to post title in post navigation below the comment? i hope you can help me.

    One more, can i? how to change the title in comment to title post like you? this is what i mean..59 Responses to “How To Add Custom WordPress Pagination Without a Plugin”

    I’m sorry, i’m newbie in blog..

    • Hi Naziman,

      Thanks for visiting my blog.
      For your first question. I wrote a tutorial bout that: How To Add WordPress Comment Pagination Without a Plugin

      For your second question:
      In your theme’s folder, open “comments.php” file, locate the comments header usually under the conditional statement:
      < ?php if ( have_comments() ) : ?>
      And replace your header with something like that:

      <h3 id="comments">
      <?php comments_number('No Responses', 'One Response', '% Responses' );?> to “<?php the_title(); ?>”
      </h3>
      

      Line 2 of the above code is what’s most important to replace.

      After that save the file and upload it to your server.
      I can’t be more specific than that, since I don’t have any info. about your theme.
      let me know, If you require more help with your specific theme but I request a donation first (button at the top).
      Boutros.

      • Naziman says:

        Thanks for your support..for the first question, actually i want to change the link below comment from previous post and next post to “post title”. Like something like this, from “next post” to “How To Add Custom WordPress Pagination Without a Plugin” below the comment.

        By the way, thank you, i also want to change the pagination for comment because right now, i’m break my comment. i will try it.

        For the second second question, thanks..i get it.. i have change that..

        • Hi Naziman,
          I missed your question here:
          To do that you need to replace the existing code for “next_post_link” and “previous_post_link” to the following code: (usually in “single.php”)

          <div class="nav prev left"><?php next_post_link('%link','&laquo;  %title'); ?></div>
          <div class="nav next right"><?php previous_post_link('%link', ' %title &raquo;'); ?></div>
          

          What’s most important in lines 2 and 3 is the PHP code.
          For an example go the the ‘Clear Line’ theme on my blog, open any single posts and see the previous/next post links with titles.
          Hope this helps.

  11. For example titled pagination would appear with posts titles (or custom fields, to prevent Google duplication penalty) instead of numbers. I’ve always found numbered pagination unintuitive. Meaning that numbers don’t give the reader a clue as to the content of the Next or Previous posts.

    Titled pagination:

    BETTER PAGINATION EXAMPLES …. INTUITIVE UX FOR WORDPRESS

    instead of:

    Previous 1,2,3,4,5…21 Next

  12. Is there any SEO advantage to using a custom field or page/post tile as a pagination link?

    • Thanks David for commenting. I am not sure I understood your question.
      The pagination link here will be: “http://…/page/2/ for page 2; “http://…/page/3/” for page 3; “http://…/page/4/” for page 4 etc.
      The SEO advantage here is that the Website will be crawled much deeper by robots and most importantly it will be much more user friendly navigating the pages from a user point of view.
      Hope this helps.
      Boutros.