Revisiting Custom Post Types, Custom Taxonomies, and Permalinks

Published 5 years 4 months ago on May 20, 2011 — 8 min read

This article is an extension of both Custom Post Types, Custom Taxonomies, and Permalinks in WordPress 3.0 and WordPress Archive Pages Based on Custom Taxonomy as WordPress has solved a number of the issues covered in those publications.

I’m using Custom Post Types more and more, especially given the Pods 2.0 roadmap. I’ve got an entire philosophy behind how I dictate what should be a Custom Post Type, a Custom Taxonomy, or a Pod, but we’ll leave that for another day. I’d like to talk about how to best architect your Custom Post Types with a focus on integrating your Custom Taxonomies to the best of your ability.

As should be a focus for us, I have a strong principle as I’m building out a site to make sure the URL structure makes sense. This isn’t for SEO, online marketing, buzzwords, or social media. This is for the real life people reading these URLs. I’m a big proponent of retaining a folder structure, and as per the now outdated publication first covering this topic, we can make things even better.

Setting up your Custom Post Type

If you’re still using the Custom Post Type UI that’s all well and good, but I would suggest eventually moving away from that and hand coding your Custom Post Types just to raise your own bar of knowledge over time.

The first thing I always do when structuring my Custom Post Type and Custom Taxonomy relationships is to have a plan. You’ll see why in a minute, but it’s really important to have a URL structure game plan before writing any code or setting anything up. I’m going to use the same example as last time and focus on the situation of wanting to have a site that focuses on cameras and camera gear. That said, I’m going to want the following:

  1. A Custom Post Type named cameras
  2. A Custom Taxonomy named brands

Note: I’ve got a policy to prefix my Custom Post Types/Taxonomies as a way of preventing undesired behavior should a client some day switch themes or something to that effect. For the sake of this example I’ll leave the Custom Post Type/Taxonomy names as is for readability.

Given this relationship, I’d like my URL structure to resemble the following: My Cameras section should find a home at http://example.com/cameras/, when viewing a single camera I’d like the URL to piggyback the ‘base’ URL and append the camera slug: http://example.com/cameras/canon-7d/.

Since the brands Taxonomy is directly related to the cameras, I’d like to also piggyback that same ‘base’ URL as we’re able to out of the box with a Custom Post Type, something like http://example.com/cameras/brands/. This can be a bit of a tricky achievement if not implemented properly. Even when implemented properly, there are a couple of things you’ll want to keep a close eye on should you go with this type of implementation.

The take-home message here is the fact that in order to piggyback the ‘base’ URL as outlined above, we need to define the Taxonomy first. It all comes down to knowing WP_Rewrite and making it your friend.

Setting up your Custom Taxonomy

Our next step (after determining our URL structure) will be to add our Custom Taxonomy:

$labels = array(
    'name'                          => 'Brands',
    'singular_name'                 => 'Brand',
    'search_items'                  => 'Search Brands',
    'popular_items'                 => 'Popular Brands',
    'all_items'                     => 'All Brands',
    'parent_item'                   => 'Parent Brand',
    'edit_item'                     => 'Edit Brand',
    'update_item'                   => 'Update Brand',
    'add_new_item'                  => 'Add New Brand',
    'new_item_name'                 => 'New Brand',
    'separate_items_with_commas'    => 'Separate Brands with commas',
    'add_or_remove_items'           => 'Add or remove Brands',
    'choose_from_most_used'         => 'Choose from most used Brands'
    );

$args = array(
    'label'                         => 'Brands',
    'labels'                        => $labels,
    'public'                        => true,
    'hierarchical'                  => true,
    'show_ui'                       => true,
    'show_in_nav_menus'             => true,
    'args'                          => array( 'orderby' => 'term_order' ),
    'rewrite'                       => array( 'slug' => 'cameras/brands', 'with_front' => false ),
    'query_var'                     => true
);

register_taxonomy( 'brands', 'cameras', $args );

The above snippet can be tossed in your theme’s functions.php and will set up our Custom Taxonomy. If we were to visit the WP Admin at this stage, we wouldn’t see any change because we haven’t created cameras yet. Take extra note of the rewrite key in our $args array. That value is telling WordPress the slug to use in our Custom Taxonomy. We don’t want it prepended with the WordPress permalink front base, which defaults to true, and we want the slug to include our Custom Post Type rewrite slug.

With our Custom Taxonomy created, we can continue on to implementing our cameras Custom Post Type.

Integrating our Custom Post Type with our Custom Taxonomy

It’s very important that your Custom Post Type snippet is placed after your Custom Taxonomy snippet in functions.php. That ties in with how WP_Rewrite works as the process of deciphering permalinks is, in a way, top-down. Our Custom Post Type will be added like so:

register_post_type( 'cameras',
    array(
        'labels'                => array(
            'name'              => __( 'Cameras' ),
            'singular_name'     => __( 'Camera' )
            ),
        'public'                => true,
        'show_ui'               => true,
        'show_in_menu'          => true,
        'supports'              => array( 'title', 'editor', 'thumbnail' ),
        'rewrite'               => array( 'slug' => 'cameras', 'with_front' => false ),
        'has_archive'           => true
    )
);

Again, it’s important that this snippet be inserted after your Custom Taxonomy snippet that wishes to piggyback the Custom Post Type slug. After saving functions.php with both snippets, your Custom Post Type and Taxonomy will be ready to use:

The next big part will be making sure your permalinks are working. One of the greatest things to be included in core since the last writing, is the has_archive key in our register_post_type() arguments. This optional argument unlocks the ability to have a traditional archive view of your Custom Post Type without needing a custom Template.

Note: Something very often forgot by new(er) Theme developers is the fact that you must visit your Settings > Permalinks options screen in the WordPress admin to ensure your new permalinks are picked up. You don’t need to edit anything, just view the page.

Template files you’ll need

To get up and running we’ll first begin with our Custom Post Type archive. While referred to as an ‘archive’ it almost seems better to refer to it as an index semantically as it’s not really an archive in the traditional sense. Nomenclature aside, we’re going to follow the WordPress Template Hierarchy and create archive-cameras.php (archive-{CPT_NAME}.php) to which WordPress will toss The Loop for our cameras Custom Post Type. We can literally copy the contents of index.php there for the time being, to be customized later.

<?php get_header(); ?>

<div id="container">
    <div id="content" role="main">
        <h1>Cameras</h1>
        <?php get_template_part( 'loop', 'index' ); ?>
    </div><!-- #content -->
</div><!-- #container -->

<?php get_sidebar(); ?>
<?php get_footer(); ?>

When that file has been created, visiting http://example.com/cameras/ will hit that template file and run through The Loop.

If you’d like to have a custom number of posts per page, you can do that too.

Again, following WordPress Template Hierarchy, clicking through to the Custom Post Type single page will use your single.php Template file, unless you create single-cameras.php. In that case, WordPress will use your CPT-specific Template file.

Now that we’ve implemented browsing Custom Post Types in our Theme, we need to include the appropriate Custom Taxonomy Template files as well. Keep in mind that this only works because of the 'has_archive' => true argument used when setting up the Custom Taxonomy. The beauty here lies in the fact that we can now create a taxonomy-brands.php Template file which will be used when browsing our Custom Taxonomy, in this case camera brands. As we did before, we can copy the lowest common denominator index.php to get started with our Custom Taxonomy archive Template file:

<?php get_header(); ?>

<div id="container">
    <div id="content" role="main">
        <h1>Brand Archive</h1>
        <?php get_template_part( 'loop', 'index' ); ?>
    </div><!-- #content -->
</div><!-- #container -->

<?php get_sidebar(); ?>
<?php get_footer(); ?>

When taxonomy-brands.php is saved, we can now visit http://example.com/cameras/brands/canon/ to view our Custom Taxonomy archive:

Clicking through will again bring us to the applicable single Template file.

There is one ‘gotcha’

Although our permalinks are working in the way we originally intended, there is an important fact to take into consideration. There is a use case, albeit unlikely, that would prevent the viewing of a Custom Post Type entry. Take for example, the possibility that we had a camera called “Brands”. With the way we’ve set up our Custom Taxonomy, that slug would never be viewed because of the top-down way our permalinks are handled. To be more generalized, if there’s potential for a Custom Post Type slug to match that of your Custom Taxonomy, use a different implementation and sacrifice your ideal URLs. This circumstance is unlikely, but it’s a drawback you should be aware of should you go after an implementation such as this.

Copyright © 2006—2016 Jonathan Christopher