Dissecting WordPress Themes Part 10: Post Type Hierarchy

In this article, we’ll finish up the archive page hierarchy with a look at archive pages for custom post types. Back in Part 3 of the series we created a custom post type in order to demonstrate the single-[post type].php template, where [post type] is the name given to the custom post type. For our custom post type, my_books, we found that the single-my_book.php template would override single.php if present in the theme directory.

WordPress also provides for archive pages of custom posts using the template pattern archive-[post type].php. Upon visiting such a page, the Loop will return a list of the custom posts.

Create Custom Post Type Archive Link

In order to demonstrate the custom post type archive template, we’ll first generate the URL required to link to the page. Since we haven’t done much with our header template yet, let’s put the link in header.php. We’ll use the get_post_type_archive_link() call and pass to it the name of our custom post type, my_book. Edit the header.php file and add the code highlighted below.

echo "<!DOCTYPE html>\n";
echo "<html>\n";
echo "<head>\n";
echo "<link rel='stylesheet' type='text/css' href='";
     bloginfo('stylesheet_url'); echo "'/>\n";
echo "</head>\n"; 
echo "<body>\n";
echo "<p>In header.php</p>\n";
echo "<a href='", get_post_type_archive_link('my_book'),
     "'</a>Books Archive\n";

Save the file and visit the home page of the site. You should see the new link as shown in the screenshot below. Hover your mouse over the link and note that the format is http://localhost/lab1/books.

Custom Post Type Archive Link

The URL of the generated link will navigate to the archive page for our my_books custom post type.

The books component of the URL corresponds to the rewrite parameter we passed to register_post_type() in our functions.php file:

add_action('init', 'myPostTypes');
function myPostTypes() {
	register_post_type ('my_book',
		array ('labels' => array (
				'name' => 'Books',
				'singular_name' => 'Book'),
			'public' => true,
			'has_archive' => true,
			'rewrite' => array ('slug' => 'books'),
			'supports' => array ('title', 'editor', 'author', 'excerpt',
				'revisions', 'comments', 'post-formats'),
			'taxonomies' => array ('category', 'post_tag')

If you now click on the link you see the archive.php page displayed as shown below. Notice that the normal posts are merged with the Book posts as they are on index.php and all of the archive pages that we’ve discussed so far.

Normal posts erroneously merged with Book posts on the custom post type archive page

The archive page for our custom post type also includes normal posts because we merged the posts on all archive pages in our functions.php file. This is undesirable on the custom post type archive page itself where we should just see Book posts. We’ll fix that by excluding this page when merging posts.

This is due to the addMyPostTypesToQuery() function that we registered on the pre-get-posts action hook in functions.php.

add_action('pre_get_posts', 'addMyPostTypesToQuery');
function addMyPostTypesToQuery($query) {
	if ($query->is_main_query() ) {
		$query->set('post_type', array('post', 'my_book'));

Notice that this function merges posts and Books on all pages. While that was our objective on the other archive pages, the Books archive page should only show Books. We can make a small change to the function to fix this problem.

add_action('pre_get_posts', 'addMyPostTypesToQuery');
function addMyPostTypesToQuery($query) {
	if (!is_post_type_archive('my_book') && $query->is_main_query() ) {
		$query->set('post_type', array('post', 'my_book'));

Here, we add a condition using is_post_type_archive(‘my_book’) to check if the current page is the Book archive. Since we’re negating the return value, posts and Books will only be merged when not on the Book archive page. Now, refresh the page and you should see only Book posts on the archive page.

Custom post type archive now just displays books

After fixing the post merge code, we now correctly see only Book posts on the custom post type archive page.

Exercise the Hierarchy

We can now test the archive-[post type].php template file for special formatting of a custom post type archive. Copy archive.php to archive-my_book.php. Change the trace message to In archive-my_book.php and add a message indicating the post type’s title as shown below.

echo "<p>In archive-my_book.php</p>\n";
echo "<p>Browsing ", post_type_archive_title(), "<p>\n";

if (have_posts()) {   // if there are posts
	echo "<table>\n";
	echo "<tr><th>ID</th><th>Title</th><th>Format</th><th>Author</th>",
			"<th>Pub Date</th><th>Category</th><th>Tags</th><th>Terms</th>",
	while (have_posts()) {   // start the loop
		echo "<tr>";
		echo "<td>", get_the_ID(), "</td>";
		echo "<td><a href='", get_permalink(), "'>",
				get_the_title(), "</a></td>";
		$postFormat = get_post_format();
		echo "<td>", $postFormat ? $postFormat : 'standard', "</td>";
		echo "<td>", the_author_posts_link(), "</td>";
		$year = get_the_time('Y');
		$month = get_the_time('m');
		$day = get_the_time('d');
		echo "<td><a href='", get_day_link($year, $month, $day), "'>",
			get_the_date(), "</a></td>";
		echo "<td>", get_the_category_list(', '), "</td>";
		echo "<td>", get_the_tag_list('', ', '), "</td>";
		echo "<td>", get_the_term_list('', 'my_terms', '', ', '), "</td>";
		echo "</tr>\n";
	echo "</table>\n";
	next_posts_link("Older posts");
	echo "<br/>";
	previous_posts_link("Newer posts");
} else {
	echo "<p>no posts to display</p>\n";

Now refresh the archive page and you should see that archive-my_book.php has overridden archive.php as shown in the screenshot below.


The template file archive-my_book.php overrides archive.php for the my_book custom post type archive page.

Check the other archive types (categories, tags, authors, dates, and custom taxonomies) to ensure that those archive pages still merge posts and Books as before.

That’s all there is to custom post type archives. The template hierarchy diagram below shows all of the templates we’ve discussed so far.

Template Hierarchy with Custom Post Type Archives

The custom post type archive hierarchy is added to the templates discussed so far in the series. This hierarchy only contains a single template file to override archive.php for a particular post type.

In this article we took a quick look at the archive page for custom post types. We first generated a link that navigates to the custom post type archive page. We then excluded this page from the code we used to merge normal posts and Books, since an archive of Books should only include Books. Finally, we found that we can provide special-purpose formatting of the custom post type archive by overriding archive.php with archive-[post type].php where [post type] is the name of the custom post type.

In the next article of the series we’ll switch our focus from posts to pages. We’ll begin with static pages, then move on to template-based pages, home/front-page pages, and special-purpose pages such as comment pop-ups and 404 pages.


Speak Your Mind