Ask your WordPress questions! Pay money and get answers fast! Comodo Trusted Site Seal
Official PayPal Seal

Trouble with custom post type pagination WordPress


My question - I hope - is simple: how do I get pagination to work correctly with custom post types?

I'm using the WP Page Numbers plugin (though I don't think that has anything to do with it) and continually get 404 errors when trying to view a long list of custom post types (ie with a url ending /page/N.

After some playing around I discovered that adding ?post_type=MY_CUSTOM_POST_TYPE onto the URL get the pagination words....but only for the first 4 or 5 pages (and there are lots more pages).

The other thing that helps is changing line 2210 of query.php from
$where .= " AND ($wpdb->posts.post_type = 'post')";
to this
$where .= " AND ($wpdb->posts.post_type = 'post') OR ($wpdb->posts.post_type = 'MY_CUSTOM_POST_TYPE')";

but clearly I don't want that as a solution.




Answers (5)


Ivaylo Draganov answers:

Hi there,

I made a quick test with custom post types and paged navigation. I tried both WP Page Numbers and the well-known WP PageNavi. At first I was also getting 404s but then I made a change to the registering function. You have to add the 'rewrite' parameter. Here's my example(custom type - Products; custom taxonomy - Catalogs):

<strong>1) The whole code:</strong>

// register custom post types and taxonomies
function register_custom_post_types() {

// register custom post type
$labels = array(
'name' => 'Products',
'singular_name' => 'Product'

$args = array(
'labels' => $labels,
'public' => true,
'capability_type' => 'post',
'rewrite' => array('slug' => 'products', 'with_front' => FALSE), // Permalinks format

register_post_type('products', $args);

// register taxonomy
$labels = array(
'name' => 'Catalogs',
'singular_name' => 'Catalog',

register_taxonomy('catalogs', array('products'), array(
'hierarchical' => true,
'labels' => $labels,
'public' => true,
'rewrite' => array(
'slug' => 'catalogs',
'with_front' => FALSE

add_action('init', 'register_custom_post_types');

<strong>2) The important part</strong>

'rewrite' => array(
'slug' => 'catalogs',
'with_front' => FALSE

You can see a simple example on my test site:

Both plugins are enabled. I have just two posts so I set maximum of one post per page.

I hope that helps solve your issue.

John Cotton comments:

Thanks IV, but that doesn't make any difference. The problem seems to be that the code doesn't know it's looking at a custom post type - nothing is being returned by the query hence the 404.

Ivaylo Draganov comments:

Could you please post the code that you are using (both for registering the post type and in the template)? And are you using a custom taxonomy or the built-in ones?

John Cotton comments:

Built in categories - but that could easily be changed if it makes a difference.

Here's the registration code:
$labels = array(
'name' => 'Directory',
'singular_name' => 'Directory Entry',
'add_new' => 'Add New',
'add_new_item' => 'Add New Entry',
'edit_item' =>'Edit Entry',
'new_item' => 'New Entry',
'view_item' =>'View Entry',
'search_items' => 'Search Entries',
'not_found' => 'No entries found',
'not_found_in_trash' => 'No entries found in Trash',
'parent_item_colon' => ''

$args = array(
'labels' => $labels,
'public' => true,
'capability_type' => 'post',
'hierarchical' => false,
'rewrite' => array( 'slug' => 'good-directory', 'with-front' => False ),
'query_var' => true,
'supports' => array('title','editor','author','thumbnail','custom-fields','revisions','excerpt','comments'),
'taxonomies' => array( 'category', 'post_tag' ),
'menu_position' => 5,
register_post_type('directory', $args);

John Cotton comments:

Template code is

add_filter('posts_orderby', 'and_by_title' );
function and_by_title( $orderby )
return $orderby . ", post_title ASC";

global $wpdb;

$paged = (get_query_var('paged')) ? get_query_var('paged') : 1;

'post_type' => 'directory',
'posts_per_page' => -1,
'post_status' => 'publish',
'paged' => $paged,
'meta_key' => '_gdBoldEntry',
'orderby' => 'meta_value',
'order' => 'DESC',
'cat' => get_category_descendent_ids($cat),

if ( have_posts() ) :
while ( have_posts() ) : the_post();
$CSSclass = get_post_meta($post->ID, '_gdBoldEntry', true) == "True" ? "awesome orange" : "notBold";

<div id="post-<?php the_ID(); ?>" class="<?php echo $CSSclass ?>">

<h4><a href="<?php the_permalink() ?>" rel="bookmark" title="link to <?php the_title(); ?>"><?php the_title(); ?></a> <?php edit_post_link('<button class="editEntry">edit</button>', '', ''); ?></h4>


if(function_exists('wp_page_numbers')) : wp_page_numbers(); endif;

remove_filter('posts_orderby', 'and_by_title' );

Ivaylo Draganov comments:

Please clarify in which template file you place this code.

And... hm... why do you have <em>'posts_per_page' => -1</em> when you want pagination? Setting it to <em>-1</em> causes all posts to be displayed regardless of other parameters. Try commenting out 'posts_per_page' or setting it to a different value.

John Cotton comments:

'post_per_page' = -1: Sorry, that's my mistake. I have it set to -1 just to see the output and check row count -normally it's 40 so that pagination appears. I forgot to set it back before cutting and pasting.

Code sits in category.php via an include_once. That's working obviously since I correctly see the first page (for a large number of different categories).


Ivaylo Draganov comments:

I think you need to include this kind of code:

// include custom post types in the loop
add_filter( 'request' , 'ucc_request_filter' );
function ucc_request_filter( $query ) {
/* Preview does not like having post_type set; feed is my personal preference. */
if ( empty( $query['preview'] ) && empty( $query['feed'] ) ) {
$my_post_type = $query['post_type'];
if ( empty( $my_post_type ) ) {
$query['post_type'] = 'any';

return $query;

It adds you custom post type to the WP query. And it will appear on your home page and category pages without having to add 'post_type' to <em>query_posts</em>. Give it a try.


Maor Barazany answers:

3 things you need:

1. Install the plugin : [[LINK href=""]]Simple Custom Post Type Archives[[/LINK]]

2. Create a tamplate to show your archive posts.
Take archive.php from your theme, duplicate it and rename the file name to <strong>type-slug.php</strong>, Where slug is the value you gave as slug parameter when you registered your post type:

'rewrite' => array('slug' => 'news'),

i.e. if the slug is news, the filename would be <strong>type-news.php</strong>

3. In the php file you created, find the loop (the line that starts with

if (have_posts()) : ?>
<?php while (have_posts()) : the_post();

and just before it add this line:


Of course, put your post type name you really have, and change the posts_per_page you want.

All this will let you use any pagination plugin to display the links to the next/prev pages.
(I ususally use the [[LINK href=""]]WP-PageNavi[[/LINK]] plugin for that)
So, then the link to page 4 for post type news will be something like:

Hope it solves your issue, I solved this way my custom post type pagination and has a lot of pages and it works just great.

Maor Barazany comments:

By the way, that is a known issue on WP3.0 and 3.0.1, they just didn't create a them hierarchy file for custom posts type, and thus all of these issues.
I think I saw in WP Dev blog that they intend to extend the template hierarchy in WP3.1 to include also file for custom-post-type's archive page, and then it will be simple as having pagination to any other posts archives.
Till 3.1, the Simple Custom Post Type Archives I suggested just do the job of having this template file hierarchy enabled and doing the correct rewrites that should be done for this to work.

John Cotton comments:

Thanks Maor, but this works when I put ?post_type=CUSTOM_POST_TYPE on the url - which is where I was with the other solutions.

You can see it for yourself here:

And then try

If you look at the source just after <!-- end header --> , you'll see which file is generating the content.

There's a whole load of debug output at the bottom of the source which constantly points to WP not knowing that the post type is a custom one as the problem (the SQL has post_type="post" in it).

That only changes when I change the post_type in the URL.



Milan Petrovic answers:

My GD Custom Posts and Taxonomies Tools Pro plugin implements archive pages for custom post types and with them pagination works fine with normal WP template or using WP Page Navi plugin for pager:

John Cotton comments:

Thanks Milan, but I'm looking for a code solution - I don't want a plugin in this case.




Adan Archila answers:

Hi John,

Try this piece of code:

<?php $paged = (get_query_var('paged')) ? get_query_var('paged') : 1; ?>
<?php query_posts('post_type=videos&posts_per_page=10&order=DSC&paged='.$paged); if (have_posts()) : ?>
<?php while (have_posts()) : the_post(); ?>
<div class="entry t">
<!-- your post content -->
<?php endwhile; ?>
<?php endif; ?>

It should work, I didnt try it with WP Page Numbers; but it actually works with WP Pagenavi.

John Cotton comments:

Hi Adan

That's exactly what I already have.

The problem seems to be that the code doesn't know it's looking at a custom post type - nothing is being returned by the query hence the 404.

It works if I put ?post_type=XXX on the end of the url.....


Adan Archila comments:

This is the way I register the custom post types:

register_post_type('testimonials', array(
'label' => __('Testimonials'),
'singular_label' => __('Testimonial'),
'public' => true,
'show_ui' => true,
'capability_type' => 'post',
'hierarchical' => false,
'rewrite' => true,
'query_var' => false,
'supports' => array('title', 'editor', 'author','thumbnail','excerpt','trackbacks','custom-fields','comments','revisions'),
'menu_position' => 5

And then when you activate your theme, and the custom post type appears on your left sidebar, go to Settings -> Permalinks and click on Save Changes, you will have to refresh your htaccess so the custom post type slug makes effect (I had that problem before)

Let me know!

John Cotton comments:

Thanks for persevering Adan.

That's pretty much what I've got (see below for code posted in reply to another answerer).

I've tried all variations and I <em>always</em> re-save my permalinks as I found that helps sometimes...but not this time :(



Duncan O'Neill answers:

It's untested, but here's my attempt ( add to functions.php )


function add_where_clause($where){
global $post_type;
if ( 'MY_CUSTOM_POST_TYPE' == $post_type ) {
$where .= " AND ($wpdb->posts.post_type = 'post') OR ($wpdb->posts.post_type = 'MY_CUSTOM_POST_TYPE')";
return $where;

John Cotton comments:

Thanks Duncan.

I need to code this a little differently as I am relying on post types to differentiate content in various parts of the site so I need to execute this only in certain places.

I'm going to give it a go.