Custom Post Type Pagination Chaining Method

Custom Post Type Pagination Chaining Method

Tutorial Details
  • Version (if applicable): 3.0+
  • Program: WordPress
  • Difficulty: Easy
  • Estimated Completion Time: 5 min.

Custom post type pagination got you down? There’s been nothing more frustrating in developing for WordPress than getting custom post type pagination to work. I’ve developed a method that’s solved my woes and I think it’ll solve yours too.


Introduction

As I’ve started to create more premium WordPress themes I’ve begun developing a base theme as a sort of framework to build on with every new project. The process was going good until I started working with custom post types. That’s when I inevitably went up against my long time foe, custom post type pagination.

Since the release of custom post types in WordPress 2.9 their pagination has proven difficult depending on the circumstance. Even heavy-hitting WordPress professionals have been stumped from time to time.

Thankfully, I feel I’ve finally beat custom post type pagination once and for all. I imagine myself flexing over it in the glow of the moonlight, my face chiseled in accomplishment, one foot planted on the ground and the other firmly on its chest.

My key to easy custom post type pagination is using the archive-posttype.php template to do something I call chaining, meaning that we use them as includes in other WordPress template files where pagination is needed. What this does is cut down on custom development for different circumstances in which a developer would want pagination. Think of it as using the custom post type archive template as an index or catch-all. There’s a few twists along the way, but that’s the big idea. Let’s begin.


Step 1 Pagination

Since coding is a foundation science lets start with the issue of pagination itself. Even though I bow to the greatness of the pagination plugin, WP PageNavi, as Jacob Goldman recently pointed out, there’s really no need for it. WordPress has its own pagination function called paginate_links() and apparently most developers know nothing about it. Be sure to read its documentation, but to save you the time of fashioning your own code for functions.php here’s mine similar to the Codex example:

function paginate() {
	global $wp_query, $wp_rewrite;
	$wp_query->query_vars['paged'] > 1 ? $current = $wp_query->query_vars['paged'] : $current = 1;
	$pagination = array(
		'base' => @add_query_arg('page','%#%'),
		'format' => '',
		'total' => $wp_query->max_num_pages,
		'current' => $current,
		'show_all' => true,
		'type' => 'plain'
	);
	if ( $wp_rewrite->using_permalinks() ) $pagination['base'] = user_trailingslashit( trailingslashit( remove_query_arg( 's', get_pagenum_link( 1 ) ) ) . 'page/%#%/', 'paged' );
	if ( !empty($wp_query->query_vars['s']) ) $pagination['add_args'] = array( 's' => get_query_var( 's' ) );
	echo paginate_links( $pagination );
}

My chaining method has been developed with this function in mind. Using WP PageNavi for custom post type pagination gets real ugly so I won’t be showing you how to do that for the same reason friends don’t let friends drive drunk. You’re welcome. But let’s move on to what you really came for – finally figuring out how to keep custom post type pagination from throwing a 404 or always reverting back to page one.


Step 2 Custom Post Type Archive Template

Since custom post types don’t have their own index pages, and like I said before, I think of the archive-posttype.php template as its stand-in specifically because I use it as the foundation for pagination in all other templates. A lot of developers will first stress a Page Template as an index page, but 1.) I obviously disagree and 2.) we’ll get to those later. Even WordPress superstar, Justin Tadlock, agrees custom post types should have at least the option of their own index pages.

Thankfully, my paginate() function out of the box with the archive-posttype.php template. Phew. But the problem is its pagination is bound by the setting for posts per page in Settings > Reading. And because custom post types are just that, custom, nine times out of ten a developer will want their posts per page to be custom as well. To do this the easiest way I’ve come by is writing a filter in functions.php like this:

function portfolio_posts_per_page( $query ) {
    if ( $query->query_vars['post_type'] == 'portfolio' ) $query->query_vars['posts_per_page'] = 1;
    return $query;
}
if ( !is_admin() ) add_filter( 'pre_get_posts', 'portfolio_posts_per_page' );

This method came to me by way of Jonathan Christopher‘s post called WordPress Posts Per Page Per Custom Post Type. Thank’s, Jonathan!

What happens if I don’t want my permalink structure to have the same name of my custom post type (e.g. http://company.com/portfolio/)? I’m glad you asked. This is one of the reasons a developer would prefer a Page Template to display their custom post types. It gives the user full control over the permalink structure because they can simply change the name of the Page that’s using that Page Template. That’s understandable and we’ll do that soon, but for those of us who don’t need to do that or want another way in the future there’s a small edit we can make under the hood to bend that structure to our will.

The function for creating a custom post type, register_post_type(), accepts an argument called rewrite. That argument’s value is passed as an array and changing the value of the slug will change the permalink structure. Here’s an example:

'rewrite' => array( 'slug' => 'insertyourpermalinknamehere', 'with_front' => true ),

After you’ve changed this go to Settings > Permalinks and hit the “Save Changes” button to dump your rewrite cache. Visit your site and refresh the page to view the change. Done and done. Again though, the only disadvantage of this method is non-tech-savvy users won’t be able to change the name of their permalink structure from the admin GUI unless of course they’re headed for the Theme Editor to change that rewrite slug.


Step 3 Page Templates

It’s not uncommon for developers to display and paginate their custom post types in a Static Front Page by way of a Page Template. The reason for this is two-fold; it gives the user that permalink structure name-changing ability we just talked about and it enables them to display custom post types on the front page of their site. But things can get ugly here. I’m ashamed to say I once coded custom post type pagination three different ways in one theme. This is partly due to using WP PageNavi, but I own this embarrassing fact to stress that this entire process can be overwhelming for theme developers who aren’t in “the know”.

Let’s call our Page Template page-portfolio.php to match the naming convention for custom post type templates even though it’s not one. Here’s the code:

/* Template Name: Portfolio */

$paged = 1;
if ( get_query_var('paged') ) $paged = get_query_var('paged');
if ( get_query_var('page') ) $paged = get_query_var('page');

query_posts( '&post_type=portfolio&paged=' . $paged );

require_once( 'archive-portfolio.php' );

This is the first time you’re seeing the archive-posttype.php template chained to another template. And it works! But what the heck is going on? This janky $paged variable business highlights the exact problem developers seem to be having with custom post type pagination. Basically if this fix (cough) isn’t in place and a user clicks to view page 2, like someone who’s been bopped over the head, WordPress gets confused and doesn’t know where it’s at. And to add insult to injury apparently WordPress knows this and accepts it as normal development procedure by publishing this note in the Pagination Parameters section of the Codex page for WP_Query():

Pagination Note: You should set get_query_var( ‘page’ ); if you want your query to work with pagination. Since WordPress 3.0.2, you do get_query_var( ‘page’ ) instead of get_query_var( ‘paged’ ). The pagination parameter ‘paged’ for WP_Query() remains the same.

It makes sense to me that developers should be able to set that variable and point to a specific page. What doesn’t make sense is why pagination inherently works with default post types (i.e. post, page, attachment), but not with custom ones.

Beyond having to patch the code there is one other catch here in that you can’t call your Page slug the same thing as your custom post type slug. Think of your custom post type slug as a reserved keyword; however, you can make the title of your Page the same name as your custom post type slug just as long as your Page slug is something different.


Step 4 Front Page Template

Using the front-page.php template locks users into a custom front page without the ability to change it (unless they delete the file or rename it temporarily). That’s why most developers opt for the Page Template method of creating Static Front Pages, but for the sake of my method let’s say we’re using the former. To achieve custom post type pagination for this template all we need to do is simply include what we’ve done for archive-posttype.php like this:

require_once( 'page-portfolio.php' );

Step 5 Custom Post Type Taxonomies

The taxonomy-posttype-taxonomy.php template works in much the same way as the front-page.php template, but instead of including the page-portfolio.php template, you’ll instead include our index, archive-posttype.php, like this:

require_once( 'archive-posttype.php' );

That completes the extent of my method. Where I was once banging my head against the desk at two in the morning I’m now calmly moving through projects at two in the afternoon. Victory! Well, at least for now. I’m not so naive as to think I may not find myself in a situation where this method doesn’t work for custom post type pagination, but I haven’t yet.


Conclusion

Unlike my other posts I hope this one becomes outdated. My hope is that WordPress will act on the recent survey that shows 92% of all developers use WordPress as a CMS and take a second look at how pagination works across their platform. The team at WordPress is nothing less than professional. I imagine a future where there’s built in pagination tools and possibly a custom post type admin page under Settings for administering posts per page per custom post type. But for now I hope this chaining method helps the many developers I’ve seen suffering on the WordPress Forums. Please feel free to download, use and abuse the following zipped files as a framework for successful custom post type pagination. Enjoy!

Custom Post Type Pagination Chaining Method Framework (ZIP)

Note: Want to add some source code? Type <pre><code> before it and </code></pre> after it. Find out more
  • http://daankortenbach.nl Daan Kortenbach

    Wow, you made my day :) I’ve spent many many frustrated hours the last months on thins. I’ll be using this, thanks!

  • Daniel

    Great article, how about getting highlited the custom post type archive item menu? isn’t working for me.

    Thanks.

  • http://pippinspages.com Pippin

    Very cool Justin. I think what I like best about your chaining method is how you’re not duplicating any code (which there is no need for). Write it once and then include.

    One thing I don’t really unrstand, though, is why did you feel it necessary to use the pre_get_posts filt to change the posts_per_page var? Why not just include it in your query_posts? You’re already doing a query to show the custom post type, so why not just add the extra parameter and avoid an unnecessary function?

    • http://3circlestudio.com/ Justin Carroll
      Author

      Thanks, Pippin! The reason for using a filter is because it works 100% of the time in my experience. There have been certain circumstances, like using a Page Template as a Static Front Page, where I haven’t been able to get the query_posts() paged query parameter to work with Custom Post Types like they do with the native Post post type.

      Or maybe I was using WP PageNavi and it did work except that plugin was causing some disturbance in the force, but I’m pretty sure it was without using that plugin. One thing is for sure, the WordPress Forums are littered with people trying to get that paged parameter to work and it just keeps failing for them by throwing 404 errors or always reverting back to the first page of results.

      I’m interested in testing some code to pin down the exact circumstance. In any case WordPress seems to have a definite pagination bug that needs fixing. The comments on Jonthan Christopher’s method article may provide some insight.

    • http://3circlestudio.com/ Justin Carroll
      Author

      The entire time I wrote “paged” I meant “posts_per_page”. I think it’s a combination of using the paged variable and the posts_per_page variable that’s not working. So I think they work with native post types and pagination, just not with custom post types and pagination. Hope that clarifies.

  • Kyle

    Maybe I’m missing somkething, but I do custom post type and taxonomy pagination al the time without having to use a hack like this:

    http://www.annzuo.ca/product-category/bridal-wedding-gown/ – taxonomy paging

    Unfortunatley do to the contract I sign wiht clients I don’t have a website I can provide to show the custom post type paging.

    I do think that what you’re suggesting would be a great core feature. I’d suggest allowing to set a page as the page of custom post type in the admin area like we do for blog posts. Then we would be able to easily have a parent page where there could be content above. Then the Title of the parent page would be easier to set, as well as the keywords. The description is set when you register the post type.

    • http://3circlestudio.com/ Justin Carroll
      Author

      Thanks for commenting! I see you’re using WP PageNavi and it works, but in my experience that plugin doesn’t work in all situations and that’s exactly the point of my method. I set out to develop a method that would work in all situations and circumstances.

      • Casey

        I still haven’t had any luck figuring this out. Does anybody have any ideas?

  • http://www.spindlecreative.net Casey

    This is EXACTLY what I needed!!! Thank you so much!

  • http://www.guillaumevoisin.fr Guillaume

    Well, great method, but I’m still encountering the same problem, 404 on page 2 in custom post type taxonomy pagination :(

    The process you present is much simpler though.

  • arnold

    thats one disturbing thumbnail btw….XD

  • http://www.spindlecreative.net Casey

    The only problem I’m having, is this method seems to remove the “current-menu-item” and “current_page_ancestor” classes from pages. Is there any way around this?

    • http://3circlestudio.com/ Justin Carroll
      Author

      Good find! I should do some testing to experience this for myself. What’s your specific situation? My knee-jerk reaction is that you may need to hook into the menu function and tell it you’re dealing with a custom page before it returns the menu. I also wonder if there’s a variable that could be set on the including page that would notify the menu. Hmm. Let us know if you make any progress.

  • laxmankasula

    Good solution for custom post pagination independent of the ‘blog pages show at most’ at backend settings. What the solution I have is dependent on the ‘blog pages show at most’ set at backend, and to work, the number we set in reading set must be lower than posts_per_page we passed via. args. I researched 2/3 days and got frustrated and set the mind, there is no other way than setting the backend ‘blog pages show at most’ lower than posts_per_page.
    Thanks for this tutorial. I just gave a try to my own project and it worked in all case.
    Awesome tutorial.

  • Daniel

    Hi,

    I have the same problem. Have a custom post type (Projects), this http://wpquestions.com/question/show/id/2272 fix the problem in single-projects.php but in http://website/projects i dont get the current in /page/2, /page/3…

    Thanks.

  • Pingback: Around the WordPress Community this Week | Inspired Magazine

  • Pingback: A Free wordpress newsletter » Theme.fm Weekly Roundup #4

  • indian

    Hi thanks for the super useful method you’ve shared with us here.

    The only problem I’m having with this is, while this worked great for creating my paginated archive, I need to loop a few posts somewhere else in my site and I can’t specify posts per page in my new loop because of the functions.php edit. What would you recommend if I need to just print the two most recent archive posts without any pagination and in a different quantity from whats on my main archive page?

  • J.Schmill

    “Beyond having to patch the code there is one other catch here in that you can’t call your Page slug the same thing as your custom post type slug. Think of your custom post type slug as a reserved keyword; however, you can make the title of your Page the same name as your custom post type slug just as long as your Page slug is something different.”

    That is mainly the reason why people keep encountering 404 page. Thanks for pointing it out.

  • Mifas

    Thanks for your code and good explanations. In my custom post type this is not worked. So I made small modification in the code.

    In the code

    function paginate($query){
    $query; global $wp_rewrite;
    ….
    }

    In the page i passed my query (new WP_Query) parameter.

    Afterward Its worked like charm and beautiful. Thanks :)

  • http://www.nicolasmesser.ch Nicolas Messer

    I’m having a blank here but how would this work with the ‘tax_query’ variable? Thanks for your help!

  • Julien Lambé

    Hi,

    great article ! You saved me a lot of tim. This setup works perfectly but i don’t find how to change the text for “next” and “previous” link. What parameters do i have to change ?

    Thanks

    • Julien Lambé

      Ok i found it. It was one of the arguments for the paginate_links() function.

  • http://www.sociatic.com Kwame

    This is Geektastic! Thanks, Justin. I’m working on a theme that is 99.something% complete and this is the only thing holding me back. If something goes wrong, I’ll come knocking on this post :).

    Cheers!

  • Pingback: Custom Post Type Slug / Page Slug Conflict | SeekPHP.com

  • http://216.22.48.224/~casadeve/events/ vaughan montgomery

    i can’t seem to get this to work.

    i think the problem with my implementation is that all the events (my custom post types) are scheduled.

    so on the page, i’m using post_status=future & the mods are ignoring it.

    how would i get it to work with that?

    my relevant part in archive-events.php looks like >

    ID); $booking_link = $booking_meta['booking_link'][0]; $event_date = $booking_meta['event_date'][0]; ?>

  • http://216.22.48.224/~casadeve/events/ vaughan montgomery

    sorry the code is

    <div id=”content_area”>

    <?php query_posts(‘post_status=future&post_type=events&order=ASC’); if (have_posts()) : while (have_posts()) : the_post(); $booking_meta = get_post_custom($post->ID); $booking_link = $booking_meta['booking_link'][0]; $event_date = $booking_meta['event_date'][0]; ?>

  • Pingback: Custom post type – add a pagination | Wondercore

  • Pingback: Custom Post Type, Pagination and 404s? | SeekPHP.com

  • Alin

    Thanks a lot for this great post. You saved me from a terrible headache:)

  • Dewey

    Hi,

    I have 2 custom post types. Channels and Episodes.

    I have people who commit episodes to particular channels. On my “single-channel.php” page, I want to be able to page through my episodes more than I do my channels. The next and prev links show up proper as ‘channel/page/2′, but when I click on them and the page reloads, the address has been re-written to just /channel, and I cannot figure out why.

    In writing this, I have become aware that I could accomplish this with a taxonomy, but I was hoping to not have to re-work all of my code this way… If you can help me out here, I would greatly appreciate it!

    Thanks,

    –d

  • http://nonsensecreativity.com Jake

    I got a problem with pagination stuff in my custom theme with custom taxonomy and post type, I tried a lot of methods and none of them work. :(, I also trying to use your method but still my pagination never work. :( my problem is whatever pagination plugins I use the pagination never show up even the wordpress build-in old style post link pagination..

  • Pingback: Justin Carroll | WordPress Custom Post Type Front Page Pagination

  • http://www.chronic-commissions.org chronic

    There was a problem with pagination at my custom wordpress template, which resolved finally. Thanks.

  • http://www.mobilemoney-machines.net mobilem

    paged=’ . $paged this attribute is very important for pagination to display and at my custom template it was missing

  • David Stones

    Hello Justin,

    I have one question about pagination and would really appreciate it if you could help me…

    I am a musician and looking into hosting my 1st website w/ ipage.com. I’ve noticed they have a 9-page limitation which doesn’t bother me because I will also use WordPress or Drupal (haven’t decided yet) as a CMS but, I would like to use one of the pages as a “featured” page to feature my latest blog posts, news, etc. I realize in time the featured page can get to be rather lengthy…

    So My question is:

    If I use pagination on my “featured” page will each page count against my 9-page limitation or will it essentially be the same (featured) page just broken into different pages via pagination’s system?

    For example:

    Let’s say on the featured page I have 7-pages using pagination. Do the use of those 7-pages mean I am now down to only 2-pages left until I exceed my ipage limitation?

    Thank you in advance for your assistance.

  • David Stones

    Hello Justin,

    I have one question about pagination and would really appreciate it if you could help me…

    I am a musician and looking into hosting my 1st website w/ ipage.com. I’ve noticed they have a 9-page limitation which doesn’t bother me because I will also use WordPress as a CMS but, I would like to use one of the pages as a “featured” page to feature my latest blog posts, news, etc. I realize in time the featured page can get to be rather lengthy…

    So My question is:

    If I use pagination on my “featured” page will each page count against my 9-page limitation or will it essentially be the same (featured) page just broken into different pages via pagination’s system?

    For example:

    Let’s say on the featured page I have 7-pages using pagination. Do the use of those 7-pages mean I am now down to only 2-pages left until I exceed my ipage limitation?

    Thank you in advance for your assistance.

  • arifur rahman

    hello, i want to show like this . with you function Now Viewing | 1-50of 122 | Next | Page(s) 1,2,3 of 3
    how can i do this.

  • markimark

    I was trying a lot for my custom post type “projects”, then I found this very nice tutorial. Thank you very much Justin.
    First it was a little difficult for me to follow the way it was written. My custom post type already existed and I had to filter out the sections I would need to make it work with the code I already had. After I worked that out, it was working just the way I wanted it, for the very first time.

    I am very thankful.

    Cheers.

  • http://www.sanjaykhemlani.com/ sanjay

    Thanks a ton! This save me a lot of time starting today, really cool post! Thanks man :)

  • Steven

    This post saved me and opened my eyes! Would have taken me forever to figure out the archive chaining method. whew!

  • http://rorymorris.co.uk Rory

    Thanks.

    I read this in an American accent and it sounded GREAT.

    Thanks again.
    x

  • Elisabeth

    First: Thank u for this :)

    Second: I’m getting a Notice message;

    ” Notice: Undefined index: post_type in C:\wamp\www\domain\wp-content\themes\mytheme\functions.php on line 575 ”

    Here is line 575:
    if ( $query->query_vars['post_type'] == ‘kundesitat’ ) $query->query_vars['posts_per_page'] = 4;

    Here is the whole code:

    function kundesitat_posts_per_page($query) {
    if ( $query->query_vars['post_type'] == ‘kundesitat’ ) $query->query_vars['posts_per_page'] = 4;
    return $query;
    }
    if ( !is_admin() ) add_filter( ‘pre_get_posts’, ‘kundesitat_posts_per_page’ );

    If I delete this code the notice also disappear, any ideas on what’s wrong here?

  • http://www.bluesmoke.ro Daniel

    Well, this works only if you have a taxonomy set in place! If you only have a custom post type and no taxonomy, this doesn’t work! I guess this is why my previous method didn’t worked either… I was hopping to find a solution that didn’t required the custom taxonomy as well.

    • http://www.bluesmoke.ro Daniel

      Nevermind! My bad! I got it!

      • Elisabeth

        Willing to share what you found out? :)

  • http://designphilic.com Tirumal

    ‘rewrite’ => array( ‘slug’ => ‘insertyourpermalinknamehere’, ‘with_front’ => true ),

    How about using $_SERVER['REQUEST_URI']; to get the current URL and modify it to get the pagination. I made it using that, you can find it here -> http://www.designphilic.com/2012/08/wordpress-custom-post-type-pagination.html

  • Anderson

    Here work, thanks!

  • http://twitter.com/pushplaybang Paul van Zyl

    I know this is an older post but something I’m still a little baffled and frustrated by. I’ve popped this in and it seems to work except the undefined index error as mentioned by another reader when putting WP in debug mode…. anyone got any ideas? would make this acceptably useful.

  • Jacob Reid

    First, this post was a great find online after unsuccessfully trying a bunch of other solutions to this problem. Big Thanks!

    I can’t, however, seem to get this to work with custom taxonomy templates. If I have a Custom Post Type for News and set my templates up via this tutorial, the main News page paginates fine. But, if I have Categories in my sidebar such as Breaking News and I click on that to filter the Custom Post Type “News” to a Custom Taxonomy “Breaking News” and I have pagination set up, it does not work.

    Any ideas?

  • Disqwalker

    One more thing, that’s broken by this method, is that styles and scripts, enqueueung in the header or footer with conditions like “is_page_template(‘page-portfolio.php’)”, doesn’t enqueueung anymore, as soon as “get_header();” and “get_footer();” calls locates in archive template