Navigation of Custom Post Type Archives in WordPress

WordPress changed when it introduced Custom Post Types – suddenly the possibilities were endless.  Wordpress had become the content management system we all were hoping it would.

But for one problem.  It was and still really isn’t clear how to include posts of  custom post types in your website navigation.  This article outlines how I do it.

Create a Custom Post Type

First I create a custom post type – let’s use the ubiquitous ‘movie’:

register_post_type('movie', array(
 'labels' => get_labels('Movie'),
 'supports' => array('title', 'editor', 'thumbnail', 'excerpt'),
 'has_archive' => true,
 'hierarchical' => false,
 'rewrite' => array('slug' => 'movies', 'with_front' => false),
 'public' => true
));

The function get_labels() is just a helper function I use to customise all the various labels required for each custom post type with a single function call.   I’ve included the source here:

function get_labels($singular, $plural = '') {
 if ($plural == '') $plural = $singular.'s';
 return array(
  'name' => $plural,
  'singular_name' => $singular,
  'search_items' => 'Search '.$plural,
  'popular_items' => 'Popular '.$plural,
  'all_items' => 'All '.$plural,
  'parent_item' => 'Parent '.$singular,
  'edit_item' => 'Edit '.$singular,
  'update_item' => 'Update '.$singular,
  'add_new_item' => 'Add New '.$singular,
  'new_item_name' => 'New '.$singular,
  'separate_items_with_commas' => 'Separate '.$plural.' with commas',
  'add_or_remove_items' => 'Add or remove '.$plural,
  'choose_from_most_used' => 'Choose from most used '.$plural
 );
}

Before we move on, go ahead and create a few dummy posts of our new post type ‘movie’.

Create a page for your custom post type archive, and add it to your menu

Next I create a page ‘Movies’ with slug ‘/movies/’.  Note that this slug of this page matches the slug defined in the rewrite property of our custom post type – this is important.

Make sure you then add your new page to the menu so that it appears in your site’s navigation.  By default, this will now load the page, and not show our recently created posts of custom post type ‘movie’ – let’s fix that.

Create a custom rewrite rule to get your list of posts of custom post type

Ultimately, we want WordPress to get both the page we created and our posts of post type ‘movie’.  But because we can’t create page templates bespoke to child pages, it’s best to tell WordPress to get the movies, and then to latch on the page later.  To do that, we’re going to use a custom rewrite rule.  Add the following code to your functions file.

// set up the rewrite
add_action( 'init', 'ft_setup_rewrites' );
function ft_setup_rewrites(){
 add_rewrite_rule('movies(/page/([0-9]+)?)?/??$','index.php?post_type=movie&paged=$matches[2]','top');
}

This basically adds a new rewrite rule ahead of the one that WordPress is currently using to locate a page from /movies/ and tells it instead to go get all posts of type ‘movie’.  I’ve also catered for paging.  With this rule in place, you’ll now find that when loading /movies/, wordpress now uses the archive.php template file to display a list of your movies.

But we’ve lost out page!

Customise archive.php to display the page content as well

That’s no problem, we can easily pull that back in.  Add the following code to archive.php before the loop:

<?php
 $slug =  parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
 $page = get_page_by_path($slug);
 if ($page) :
  setup_postdata($page);
?>

Using get_page_by_path($slug) ensures that child pages work as well as root pages.

Now I can use template tags (i.e. the_content(); etc.) as normal to get anything from our ‘Movies’ page, and all I need to do to return to our standard loop through the posts of type ‘movie’ is reset the post data and close our if statement:

<php
  wp_reset_postdata();
 endif;
?>

Alternatively, if you want to be able to call a template with a loop in it (i.e. while (have_posts()) : the_post(); etc), you can use the following code:

<?php
 $slug = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
 $page = get_page_by_path($slug);
 query_posts('post_type=page&p='.$page->ID);
?>
...
<?php wp_reset_query() ; ?>

This template file will now work for any custom post type we create – movies, books, products.  If we want our movie archive to display a little differently from archives of posts or other custom post types, we can further customise our movie template by saving ‘archive.php’ file as ‘archive-movie.php’ and making further customisations to this file.

How do you like that!?

"a great communicator with an easy going yet professional and articulate manner"

Denise Ross
UX Designer Barclays Bank PLC

Think we may be able to help you? Why not start a conversation - chances are, you'll go away with some new ideas and knowledge.