Steve Taylor photo

Better WordPress pagination


UPDATED 6/6/09: Added query string to work with search pagination.

Ever wanted to have better pagination controls in WordPress? Those “Newer posts / Older posts” links are a bit limited. Much better is the ubiquitous numbered list, a la Google’s results page.

There is at least one plugin for this. As ever, I’m keen to keep plugin overhead to a minimum, and where possible to just include a simple function in my custom themes for basic functionality like this. Here’s the function:

function pagination( $query, $baseURL = '' ) {
	if ( ! $baseURL ) $baseURL = get_bloginfo( 'url' );
	$page = $query->query_vars["paged"];
	if ( !$page ) $page = 1;
	$qs = $_SERVER["QUERY_STRING"] ? "?".$_SERVER["QUERY_STRING"] : "";
	// Only necessary if there's more posts than posts-per-page
	if ( $query->found_posts > $query->query_vars["posts_per_page"] ) {
		echo '<ul class="paging">';
		// Previous link?
		if ( $page > 1 ) {
			echo '<li class="previous"><a href="'.$baseURL.'page/'.($page-1).'/'.$qs.'">« previous</a></li>';
		// Loop through pages
		for ( $i=1; $i <= $query->max_num_pages; $i++ ) {
			// Current page or linked page?
			if ( $i == $page ) {
				echo '<li class="active">'.$i.'</li>';
			} else {
				echo '<li><a href="'.$baseURL.'page/'.$i.'/'.$qs.'">'.$i.'</a></li>';
		// Next link?
		if ( $page < $query->max_num_pages ) {
			echo '<li><a href="'.$baseURL.'page/'.($page+1).'/'.$qs.'">next »</a></li>';
		echo '</ul>';

A little explanation is in order…

The function has been written with “custom loops” as well as the standard WordPress Loop in mind. It can be used when you define your own loop with the WP_Query object, as well as on normal templates that use the have_posts() construct. So, for custom loops built using the instructions in the last linked article, pass $recentPosts (or whatever you call your custom loop query object) as the first parameter. On normal loop templates, pass $wp_query. In either case, this query object contains some very useful info (like max_num_pages) that really help keep the above code simple.

I’ll leave the second parameter up to you to construct. It just has to be the basic URL of the listing page, onto which pagination stuff will be appended. It should include the trailing slash.

I’ve also included the query string in the right place, which should make this function work for pagination on WordPress search results. One oddity in this respect is that when you have something other than the front page of your site as your main post listing page (on this site it’s /blog/), the paging URLs don’t seem to work. So /blog/page/2/ just returns a 404. I’ve fudged around this by setting the $baseURL parameter of the call to this function on the blog to just / (the root). It kind of works. Anyone know why including the page slug doesn’t work? UPDATE 8/10/10: Whatever was causing this oddity seems to have vanished in a recent WP version. Now, just pass $baseURL as normal. I’ve also updated the above code to default $baseURL to the site root.

Well, the rest should be self-explanatory. Again, as with many of my WordPress tips here, this is aimed at theme developers who know their way around WP and PHP a bit. If you’re not quite there yet, maybe try the plugin I linked to at the start of this post…

Oh, as an added bonus, the CSS I use for the pagination list!

ul.paging { list-style: none; padding: 0; font-weight: bold; }
ul.paging li { float: left; margin: 0 6px 6px 0; }
ul.paging li a, ul.paging { display: block; padding: 3px 6px; background-color: #f5f5f5; border: 1px solid #ccc; }
ul.paging { background-color: #000; border-color: #000; color: #fff; }


  1. WP Tim avatar WP Tim

    Please post the where this function is called or how to incorporate in a page. Thanks!

  2. Steve Taylor avatar Steve Taylor

    Tim, you would just call the function where you want the pagination controls. The basic call (say, after The Loop in index.php) would be:

    pagination( $wp_query, "/" );

    If often include one before The Loop too, but set to only appear if not on the first page:

    if ( $paged ) slt_pagination( $wp_query, "/" );

    For archive.php, you’ll probably have to set up the $baseURL parameter dynamically, depending on the type of archive being shown. (Hint: the WP functions get_category_link() and get_month_link() help out a lot!)

    If you’re not following, then it’s possible that a plugin might be your cup of tea. For the most part I’m addressing theme developers here.

    Good luck!

  3. sean avatar sean

    Hello, I’m trying to improve my search.php page with your code. No more than 5 posts should be displayed per page.

    Here’s the code before yours was added:

    <a href="" title=""></a>
    Brand: <!-- End of Byline Containing Div -->
    <a href="" title="">more...</a>

    Here’s how I’ve added yours:

    if ( !$page ) $page = 1;
    $qs = $_SERVER["QUERY_STRING"] ? "?".$_SERVER["QUERY_STRING"] : "";
    // Only necessary if there's more posts than posts-per-page
    if ( $query->found_posts > $query->query_vars["posts_per_page"] ) {
    echo '';
    // Previous link?
    if ( $page > 1 ) {
    echo '<a href="'.$baseURL.'page/'.($page-1).'/'.$qs.'" rel="nofollow">« previous</a>';
    // Loop through pages
    for ( $i=1; $i max_num_pages; $i++ ) {
    // Current page or linked page?
    if ( $i == $page ) {
    echo ''.$i.'';
    } else {
    echo '<a href="'.$baseURL.'page/'.$i.'/'.$qs.'" rel="nofollow">'.$i.'</a>';
    // Next link?
    if ( $page max_num_pages ) {
    echo '<a href="'.$baseURL.'page/'.($page+1).'/'.$qs.'" rel="nofollow">next »</a>';
    echo '';
    } ?>

    <a href="" title=""></a>
    Brand: <!-- End of Byline Containing Div -->
    <a href="" title="">more...</a>

    No Results Found
    Please try your search again using different keywords.

    This is my first attempt at pagination, but I’ve used WordPress for 5-6 projects now.

  4. Steve Taylor avatar Steve Taylor

    Hi Sean, from the code you posted it’s not really clear what your issue is. Could you be more specific as to what the issue is? (Please don’t post more code unless necessary, it needed a lot of tidying up!)

    If you don’t understand the code enough to explain what the problem is, you might be better off with a plugin – check out the link at the top of this post.

  5. ayman avatar ayman

    how can i limit number of pages that displayed ?not all pages

  6. Steve Taylor avatar Steve Taylor

    Hi Ayman, you mean the number of total pages? Or to abbreviate the pagination numbers when there’s a lot, like this?

    « previous 1 2 3 4 ... 23 24 25 next »

    Abbreviating the pagination numbers is certainly something I’ll try and get round to one day – should be pretty easy. Maybe one of the current pagination plugins does that?

    Regarding limiting the total number of pages, these pages should help:

  7. Pete avatar Pete

    Am I the only one that’s get recursive querystring addition when implementing this pagination script?

    I have had to alter the make up of the href a little, but the bulk of the code is still the same.

    I could really do with a little bit of help with this one.

    Eg: http://website/?s=lorem&paged=2&paged=1&paged=3 this just keeps getting longer and longer dependent on how many links are clicked. Am I just going to have to write a string manipulation function to sort this out?

  8. Steve Taylor avatar Steve Taylor

    Pete, could you give a URL to an example where this is happening? The only thing I can guess at from your sample URL is that perhaps the script doesn’t work properly without “pretty permalinks“. I’m not sure why you wouldn’t have a pretty permalink structure, but if you’re stuck with “ugly” query string-based links—well, it should be easy to adapt. Do post your solution if you need to add anything.

  9. ayman (9th January 2010) and Steve, to limit the number of pages displayed change

    if ( $i == $page ) {
    echo ''.$i.'';
    } else {
    echo '<a href="'.$baseURL.'page/'.$i.'/'.$qs.'">'.$i.'</a>';


    if ( $i == $page ) {
    echo ''.$i.'';
    } elseif ($i < 10) {
    echo '<a href="'.$baseURL.'page/'.$i.'/'.$qs.'">'.$i.'</a>';

    P.s thanks steve you rock!

  10. Steve Taylor avatar Steve Taylor

    Thanks Peter! (BTW, do use for snippets, saves on formatting issues.)

  11. Hey Steve! First of all, great site.

    Secondly, I have quick question:

    If you notice on my “News” page (, when you click on the “2” at the bottom of the page, it loops back to the same page. I know I have more than 10 posts, so why isn’t it showing the other ones like on the home page?

    Any help is appreciated! Thanks!

    – Ryan

  12. Steve Taylor avatar Steve Taylor

    Ryan, glad you like the site! As to your issue, hard to tell what’s going on from the outside. However, it seems clear that the issue is probably nothing to do with this pagination code. The URL is showing the same as, so WP’s paging itself isn’t working. The only thing I can spot as a possible starting point is that there’s no trailing slash on the URLs. If one is added, it gets removed. I can’t remember whether it’s a server-level thing or a WP thing, but in my experience the normal behaviour is for trailing slashes to be added if they’re missing. I wonder if the paging URL without a trailing slash isn’t being recognized by WP? If that lead goes nowhere, maybe check the WP forums. Good luck!

  13. This is great. However I found a way to eliminate the need of $baseUrl. You can add the following to make the $baseUrl for any category eliminating the second argument to the function.

    $url = get_bloginfo('url');
    $baseUrl = '';

    $category_name = $query->query_vars['category_name'];

    if (is_string($category_name)) {
    $category_id = get_cat_ID($category_name);
    if (isset($category_id)) {
    $category_link = get_category_link($category_id);
    if (is_string($category_link)) {
    $baseUrl = $category_link . '/';

  14. Steve Taylor avatar Steve Taylor

    Thanks Nadeeshyama. I still find $baseURL useful for when you’re outputting posts in a loop on some custom page separate from the main blog, e.g. /news/. This is usually when I’ve fudged different post “types” using categories, though of course WP 3.0 custom post types will make this kind of thing easier, and maybe $baseURL becomes even more relevant?

  15. Hi, to anyone having problems with WordPress pagination (ie /page/2/ is shown exactly as the first page) try this:

    add to whatever query you’re having .’&paged=’.$paged and it should work

  16. Maja avatar Maja

    Vassilis that is exactly what i need (i have the pge 2 problem), but i’m no good with php :( can you please be more specific where should i put that code?


  17. Emil avatar Emil

    This cannot be any easier, really. Simply add

    where you want the pagination to show and you’re all set. This is after you paste the code Steve provided for your functions.php file.

    Just make sure you copy the codes from plain text instead copying to clipboard.

    Great job Steve, I appreciate the work,
    PS Works with 3.0.1 as well.

  18. Dan avatar Dan

    Thanks for the code.
    I also don’t like adding lots of plugins. This is a great way to achieve the target in a simple mod in the template

  19. skinofstars avatar skinofstars

    Thank you. So much tidier than using a plugin. Works just as well in wp3.0.1. Good stuff.

    Also thanks to Nadeeshyama Talagala above for doing the category $baseURL code.

  20. Ben avatar Ben


    Absolutely love this bit of code because I detest unnecessary plugins. So thanks Steve for this.

    Just wondering if anyone had figured out a way of doing the pagination when there are a lot of pages like:

    Had a go myself but I am not much of a coder so got nowhere fast! Any help or a mod would be very much appreciated. Say limit it to the first 5 page links and then the very last page?

  21. Hi Ben, this is on my “to do, but might not get done forever unless a project needs it” list I’m afraid! If anyone comes up with anything, do post…

  22. James avatar James


    Nice function..Can I ask how I would implement it for Paginate Comments ?


  23. James, I’ve never dealt with paginated comments so I’m not sure about this. As with the above code for pages, though, I’m sure there’s a plugin somewhere to do the same thing…

  24. Matt P avatar Matt P

    Have you figured out a way to get pagination working with “pretty permalinks”? I noticed you have a similar structure as mine “”. Noticed your pagination under “blog” passed you back to your main page. I just get a 404 error on mine. Any ideas?

  25. How embarrassing, my code not working on my own site! :-| As you’ll see, I’ve updated my explanation of this in the post. I’m not sure, but I imagine something must have changed in a recent WP upgrade which means that now, if your main posts page is /blog/ (as it is here), you need to pass that instead of just / as the $baseURL parameter. Of course this is as it should be. A case of a fixed bug breaking a workaround… Let me know if you see exactly what’s happened.

  26. Jewel avatar Jewel

    Do you know how to paginate a custom loop?

    Here is my query post:

    query_posts(‘paged=’ . $paged . ‘&cat=’ . $categori .’&year=’ . $year .’&w=’ .$week );

    I would like to paginate it by week. so that the viewer can navigate list of posts by pressing “next week” & “previous week” button.

    Thank you,

  27. Hi Jewel, the above code works fine with custom loops using query_posts. As for standard loops, just pass $wp_query to the pagination function, as described.

    I’ve not done pagination by week, or by anything other than a number of posts per page, sorry.

  28. schiz avatar schiz

    Hey Steve,

    Your code helped me a lot, however you should change $baseURL from just “/” to get_bloginfo(‘wpurl’) and then add a slash to the code. My blog was situated at and by using “/” it tried searching on

  29. Schiz, you’re right, thanks. I always install WP in the domain root, but I guess not everyone does. However, I’ve gone for get_bloginfo( $url ) instead of get_bloginfo( $wpurl )—I think the former is for the front-end.

  30. Hi Prakash, I don’t know whether there’s anything in the theme you’re using that might conflict with this, but it shouldn’t. The example URLs you gave seem to be fine as far as they go, but I don’t see the pagination code there, so it’s hard to know what’s going wrong. Just make sure you pass the right query object to the function. If you’re not sure what this is about, maybe you’d be better using one of the many pagination plugins out there?

  31. Hi steve, Thanks for your reply. In that page i cant identify where it is to pass query object to the function because its completely based on hooks type and we used widget to display the category post on index page.. did u see the pagination that bottom of the index page? its automatically displayed with help of function. for your ref this concern developed this theme and also gave the demo on that website thank you

  32. Hi Prakash, I’m very sorry but it’s hard to understand exactly what your problem is. Maybe you could post some code (best to use something like and link to it).

  33. //my home page of this theme

    <script type="text/javascript" src="/themes/church/cufon-yui.js”>

    <script type="text/javascript" src="/themes/church/Diavlo_Light_300.font.js”>

    Cufon.replace(‘h4.widgettitle’, { fontFamily: ‘Diavlo Light’ });

    Cufon.replace(‘div.widgetwrap h2 a’, { fontFamily: ‘Diavlo Light’ });

    Widgets screen. There you can drag the Genesis – Featured Posts widget into the Featured Top Left widget area on the right hand side. To get the image to display, simply upload an image through the media uploader on the edit page screen and publish your page. The Featured Posts widget will know to display the post image as long as you select that option in the widget interface.”, ‘genesis’); ?>

    Widgets screen. There you can drag the Genesis – Featured Posts widget into the Featured Top Right widget area on the right hand side. To get the image to display, simply upload an image through the media uploader on the edit page screen and publish your page. The Featured Posts widget will know to display the post image as long as you select that option in the widget interface.”, ‘genesis’); ?>

    Widgets screen. There you can drag the Genesis – Featured Posts widget into the Featured Bottom widget area on the right hand side. To get the image to display, simply upload an image through the media uploader on the edit page screen and publish your page. The Featured Posts widget will know to display the post image as long as you select that option in the widget interface.”, ‘genesis’); ?>

  34. Sorry Prakash, I’m going to have to give up on this. The code you’ve posted is totally irrelevant and “give me a solution” is a bit rude when I’m trying to help you for nothing. Best of luck!

  35. WP_user avatar WP_user

    Thanks, good function.
    With my little modification, it now shows links on my site in this way:
    « previous 1 … 4 5 [6] 7 8 … 42 next »

  36. here it is:

    function pagination($first=1,$last=1,$middle=5,$baseURL=false,$wp_query=false ) {
           if(!$baseURL) $baseURL= get_bloginfo('url');
           if(!$wp_query)global $wp_query;
           $page = $wp_query->query_vars["paged"];
           if ( !$page ) $page = 1;
           $qs = $_SERVER["QUERY_STRING"] ? "?".$_SERVER["QUERY_STRING"] : "";
           if ( $wp_query->found_posts > $wp_query->query_vars["posts_per_page"] ) {// Only necessary if there's more posts than posts-per-page
                   echo '<ul class="paging">';
                   if ( $page > 1 ) { // Previous link?
                           echo '<li class="previous"><a href="'.$baseURL.(($page==2)?('page/'.($page-1).'/'):'').$qs.'">« Previous</a></li>';
                   for ( $i=1; $i <= $wp_query->max_num_pages; $i++ ){ // Loop through pages
                           if($i<=$first || $i<=$middle && $page<$middle || $i>$wp_query->max_num_pages-$last || $i>$wp_query->max_num_pages-$middle && $page>$wp_query->max_num_pages-$middle+1 || $i>$page-ceil($middle/2) && $i<=$page+floor($middle/2)){
                                   if ( $i == $page ) { // Current page or linked page?
                                           echo '<li class="active">'.$i.'</li>';
                                   } else {
                                           echo '<li><a href="'.$baseURL.(($i!=1)?('page/'.$i.'/'):'').$qs.'">'.$i.'</a></li>';
                                   echo '<li class="dots">...</li>';
                   if ( $page < $wp_query->max_num_pages ) { // Next link?
                           echo '<li><a href="'.$baseURL.'page/'.($page+1).'/'.$qs.'">Next »</a></li>';
                   echo '</ul>';
  37. A little late to the party here but thanks for code snippet. command + C & command + V was a tough one but I managed. I did have to modify your CSS for color coordination. In the future I’ll expect a little more effort on your behalf ;)

    Thanks again.

    P.S. – Are still using the script?

  38. Hi Justin, glad you coped – I do leave most of the burden for my readers ;)

    Yeah, I still use it as a default, though more often these days clients are going for a Facebook-style AJAX “more” button.

  39. Well, It’s been filed nicely away in my code snippets. I’ll now waiting for AJAX solution ;)

    Thanks again.

  40. Sandip avatar Sandip

    Hi i use wordpress for this and i want to display pagination at front side without reloaded page.

  41. Hi, could you put long bits of code on, and be more specific with your question? Sounds like you need an AJAX version of this script, which I don’t have at the moment.

  42. Nice piece of code iron out a few issues and it could become a very useful function

    $wp_query is globally available, i might be wrong but i can’t think of any situation where you’d need to pass the query.

    I don’t know if this cased the earlier issue but default values for arguments can’t be functions, they have to be constants in php, i’m confused at how this worked at all as that usually throws a fatal error.

    The function won’t work with default permalinks.

  43. @Jonny, thanks for the heads-up on the default argument, I’ve corrected it. My own version of this has changed a lot for my needs, I just added that blindly on the advice of a previous commenter.

    I’ve had situations where I need to pass a custom loop query, so that facility is there if needed. Just pass $wp_query if you want.

  44. David H avatar David H

    Hey Steve, you are probably so tired of responding to this thread by now….

    I am wondering, of course, if there is an expanded way to paginate now that things like Jquery Tabs are popular on the home page of various WordPress themes.

    For instance, in a classified ads theme which also uses some modified wp code to paginate, there is a default index page Tab display of various queries.

    Tab one might be a query run for the “Latest Ads”–if the query is set for, say, 30 ads, then this tab will line up the short “posts” of ads and show “Page 1 of 3” at the bottom of that tab view. If I click on the number 2 the link will be “/page/2/” and the next set of listings appears inside the same tab.

    However, the PROBLEM starts when I click Tab Two—–If Tab 2 “Featured Listings” (using “sticky”) is clicked then I see the listings (that are all marked “sticky”) and at the bottom of the tab I may see “Page 1 of 4”. But to click, say, number 2 will produce the url “/page/2” again–the same url as in Tab One.

    On clicking “2” in the “Featured Listings” Tab the resulting listings will now be thrown back to the Tab one view and I will be looking at the Tab One “Just Listed” second page of listings.

    Some people have said that only an Ajaxed version can help create separate Tabs pagination that is correct for the query that produces those types of listings.

    I am thinking that there must be a function that could run through the queries in the Tabs block of code and produce at one time the Complete list of “/page/1….infinity/”, accumulating all of the paginations for 2 or 3 tabs of separate queries, whether by iterating “/page_just_listed/3/” and “/page_featured/4/”, or by creating , or,

    just creating the Total pagination in one string for all three tabs in a way that while we are in Tab two the pagination stays in the Tab two view while “/page/12/” is being served.

    It is the jquery Tabs invention that creates a need for a pagination that matches its needs.

    Steve, can you think of a way to do this without Ajax?

    Thank you for your consideration!

  45. David, I’ve used jQuery tabs but not for this kind of thing. I can’t really grasp the issue here without setting the whole thing up and I’m working to a tight deadline at the moment. Maybe try the jQuery Tools forum or the wp-hackers list? Good luck!

  46. Hello Steve, I wanted to say thank you for the function you shared with us.

    I’ve found in very useful and decided to improve it a little.
    Now it shows just a range of pages before and after the actual one and also 2 links, one for the first and one for the last page available, but only when the current page is not too close to one of them.
    I’ve made it work both if the site uses “pretty permalinks” or the standard permalinks structure.

    I’ve pasted the function, together with a small helper function on pastebin, just in case you were curious and wanted to give it a look..

    I hope this is useful to others as your one was useful to me..

    bye ;)

  47. Danilo, many thanks for posting this. All my work is project-driven at the moment, so maybe one day a project will demand better pagination and I’ll improve my code!

  48. Duxx avatar Duxx

    For me its not working, shows the page links but when i click on it nothing happens

Comments are closed.