Custom post type and taxonomy hierarchy in WordPress

For this blog post I’m going off the assumption you have a fairly good understanding of WordPress custom post types and taxonomies.

Let’s say you’re building a website for a company that provides a list of services. You could create a custom post type for services using the code below.

 
$args = array(
      'public' => true,
      'label'  => 'Services'
    );
    register_post_type( 'service', $args );

Next thing you would want to do is create a standard WP page from where you will pull a list of all your custom post types and display them in a nice format for the user to make his/her selection to read up about that specific service by going to the service single page. You’ll likely create a page with the slug ‘/services/’ for this page.

By default the slug for your services custom post posts would be ‘/service/xxx’, which means when you’re on your ‘/services/’ page and you click on a single service your URL would change to ‘/service/xxx’ and for SEO that’s really bad.

A possible solution to this could be to rewrite your custom post type slug. You could do this as follows:

 
$args = array(
      'public' => true,
      'label'  => 'Services'
      'rewrite' => array('slug' => 'services'),
    );
    register_post_type( 'service', $args );

Now both slugs are the same, but you face yet another problem. If you click back to the base page on your page breadcrumbs for example, you won’t ever see your standard WP page you created, you’ll see your custom post archive page. The ‘/services/’ base page is an archive page, not a WP page.

That you fix by doing this:

 $args = array(
      'public' => true,
      'label'  => 'Services'
      'rewrite' => array('slug' => 'services'),
      'has_archive' => FALSE,
    );
    register_post_type( 'service', $args );

You disable the archive page.

Perfect.

Now. Let’s say you have different types of services and you want to categorize those services. You would create a custom taxonomy like this:

	
register_taxonomy(
		'service-categories',
		'service',
		array(
			'label' => __( 'Services Categories' )
		)
	);

The problem once again is in that your slug structure would be ‘/service-categories/’ for your custom taxonomy. You solve this once again by rewriting the URL:

	
register_taxonomy(
		'service-categories',
		'service',
		array(
			'label' => __( 'Services Categories' ),
			'rewrite' => array( 'slug' => 'services' ),
			'hierarchical' => true,
		)
	);

Let me expand on my services example. Let’s say our company does plumbing and gas installations. We could create a taxonomy for ‘plumbing’. If I create a services custom post for ‘unblocking drains’ and tag it as ‘plumbing’. I would expect my URL for that page to be ‘/services/plumbing/unblocking-drains/’, but WordPress does not give you that by default. Your URL would just be ‘/services/unblocking-drains/’. To get your taxonomy in your URL structure you have to rewrite it once again. You’ll do that with a filter and a function:

add_filter('post_type_link', 'my_awesome_post_link', 1, 2);

function my_awesome_post_link($permalink, $id){
//I'm going to let you write this for yourself
//My code is very specific to the project I did this for and won't work for you.
//Essentially what you need is to create the URL structure to include your taxonomies in the URL

//for example:
//return home_url().'/services/plumbing/unblocking-drains/';
}

This function will then create your link and everywhere you call

get_permalink($id_of_post)

it will return the full path including your taxonomy structure.

But this isn’t all, now that you’ve created the URL structure you need to tell WP how to interpret it. You need to create a rewrite rule for it.

add_filter('generate_rewrite_rules', 'my_rewrite_rules');
function my_rewrite_rules($wp_rewrite) {
//I'm going to let you write this for yourself
//My code is very specific to the project I did this for and won't work for you.
//You need to create a rewrite rule for each of your custom post types including the full URL structure

//for example:

$custom_rules = array();

//for each of your custom posts:
$custom_rules['^services/plumbing/unblocking-drains$'] = 'index.php?service=unblocking-drains';

$wp_rewrite->rules = $custom_rules + $wp_rewrite->rules;
return $wp_rewrite->rules;
}

What this does is when you give WordPress this URL ‘/services/plumbing/unblocking-drains/’ it converts it to ‘index.php?services=unblocking-drains’ which WordPress is able to understand and return the correct content.

This is what I did in a nutshell.

I (re-)learned a couple of valuable lessons:

  • Unit testing
    • Whenever you start something that seems like it’s going to be small, just a few functions, it always ends up being more. A few hours later you sit with a class with a bunch of methods. You’re going to wish you started writing unit tests in the beginning.
  • Test data
    • You’re going to need real-world test data. Lorem ipsum doesn’t mean anything. Sometimes having real-world test data shows you certain edge-cases you would not have thought of when using generic data.