Sometimes a WordPress custom post type (CPT) needs a custom taxonomy to match. At first, I created a CPT without its own custom taxonomy. The result was that my CPT posts shared the same taxonomy as blog posts. (To clarify, that shared taxonomy included the standard WordPress Categories and Tags.) As a result, I encountered several functional limitations from my CPTs and blog posts sharing the same taxonomy that I could only resolve with the creation of a WordPress custom taxonomy. Consider this lesson learned!
Just looking for code to make your own custom taxonomy? Jump ahead.
Problems without a Custom Taxonomy
Functionality problems arose later due to a shared taxonomy between my custom post type and a one-size-fits-all taxonomy. I created my CPT first. (To make a custom post type, check out my tutorial.) Then, I thought I could create categories to fit my needs. However, I encountered a few issues.
For example, I wanted users to find my CPT posts when searching the search. This way, both CPT posts and blog posts show answers to the user’s queries. I enabled CPT posts to show in search results with a simple functions.php update. However, if I customized a page template to query my (blog) posts, the blog posts and CPT posts were no longer separate. Indeed, a query aimed for one showed results for both.
One place these problems showed was in my Featured Case Study footer widget. Ultimately, the Featured Case Study widget showed blog posts and CPT posts, even though blog posts weren’t relevant. Additionally, a secondary menu I made to explore my blog post categories also displayed the CPT-specific categories.
Band-Aid Fixes
To get around my WordPress taxonomy issues, I made two parent Categories and assigned my blog post and CPT categories to one or the other.
While this was a great short-term fix, I now created SEO issues.
First, I inadvertently created duplicate URLs. For instance, my landing page for my blog (the blog archive page stylized by my theme) shared the URL as the new Blog parent category: www.laralee.design/blog.
Of course, duplicate content is a SEO-penalty.
It’s also confusing to users, resulting in a second problem. When users click the breadcrumb to the Blog, the website lead them to the category archives instead of my landing page. This inadvertent redirect defeats the purpose of a breadcrumb. It took users to a new webpage instead of their previous one.
As a result of the redirect, my visitors also failed to receive the benefits of the landing page, such as a blog menu to jump right to their topic of choice.
Finally, I encountered a third shared taxonomy problem. Because shorter headlines and URLs perform better, I wanted to remove the base slugs from my blog permalinks and create pretty links with just the post name.
However, removing the category base slug from my blog posts also removed them from the CPT posts, in which I wanted to keep the “case-studies” in the URL.
Furthermore, because the duplicate URLs tied their fates together, I disabled my Case Study landing page completely by removing the post slugs creating a 404 error.
Continue the Out-of-the-Box Taxonomy vs. Make a Custom Taxonomy
Now that I discovered these problems, I realized I had two paths forward to a solution.
One, I continue with the non-destructive edits to preserve what I have and keep it working. It means she back-tracking and settling. For example, I can’t remove the base slugs for that SEO boosts. However, my permalink structure remains unchanged. Changing permalink can be devastating to SEO as users and search engine crawlers alike struggle to find the re-placed content. Since that could have profound negative impact, this solution isn’t bad, but my website wasn’t going to be as good as it could be.
The second solution means splitting my taxonomy into blog categories and CPT categories….and new permalink. After the initial devastation, the new permalink structures are better for SEO. This solution also enables me to make changes to one set of posts without impacting the other set to really tailor my content and user experience to the post types.
Since my website is fairly new and not super-popular yet, I decided to pursue solution two as its best practice.
With a new custom taxonomy, I completed my other SEO edits such as shortening my permalinks to just the post name.
I was also able to split my category pages between blog posts and CPT posts. Now, I could choose to disallow/noindex one set and allow/index the other set. I could also create different layouts for each archive page, like including a category description or not.
Although my website has taken a hit from Google as its crawlers are re-learning how to navigate my website, these changes will make my site easier for users to browse my content.
How I Made a Custom Taxonomy in WordPress
Copy-paste the code snippets below into your child theme’s functions.php document.
In my example, I created a custom taxonomy called “Services.” This taxonomy works together with my custom post type, Case Studies. As a result of my custom taxonomy, I can label and categorize a case study with the creative services and skills I used while completing the project the case study highlights.
Next, let’s break down what the code does.
1. Create a function and name appropriately.
First, be sure to include opening and closing curly brackets. Then, it’s a matter of preference whether to name the function first or initialize it. Some programming languages are particular about the order, although PHP isn’t. Because of my experience writing vanilla JavaScript, I tend to declare functions first then initialize.
function case_studies_custom_taxonomy() {
// Stuff about $labels and register_taxonomy
}
2. Name the custom taxonomy using labels.
This portion of the code is rather standard. Spell out the singular and plural usage of the taxonomy name. Simply swap “Service(s)” with your own custom taxonomy name. Lastly, double-check that closing parentheses and semi-colon to end the array( ) argument is there.
function case_studies_custom_taxonomy() {
$labels = array(
'name' => _x( 'Services', 'taxonomy general name' ),
'singular_name' => _x( 'Service', 'taxonomy singular name' ),
'search_items' => __( 'Search Services' ),
'all_items' => __( 'All Services' ),
'parent_item' => __( 'Parent Service' ),
'parent_item_colon' => __( 'Parent Service:' ),
'edit_item' => __( 'Edit Service' ),
'update_item' => __( 'Update Service' ),
'add_new_item' => __( 'Add New Service' ),
'new_item_name' => __( 'New Type Name' ),
'menu_name' => __( 'Services' ),
);
}
3. Register the new taxonomy with the WordPress database
Within the function, add more code to instruct WordPress how to process the new taxonomy. There are several attributes to customize:
- Hierarchical: allow nested taxonomy terms for sub-categories and the like
- Labels: populate with the values entered in the $labels array earlier in the function
- Show UI: make the taxonomy visible in the WordPress dashboard
- Show Admin Column: display a column in the “All Posts” view for the custom taxonomy
- Query Var: allows developers to call the custom taxonomy as an object
- Public: make the taxonomy visible on the public website, creating archive pages and such for the taxonomy terms
- Rewrite: sets the slug name for the custom taxonomy (Remember, no spaces, and hyphens are preferable to underscores for pretty links.)
WordPress has even more values than these, but these are the most commonly used. To check out what else you can customize in a taxonomy, visit the WordPress Codex.
I felt like my custom Services taxonomy terms were self-explainable. I also didn’t feel like they needed to gather my Case Study CPT posts into term archives. Therefore, I set the value for “Public” to false to prevent my custom taxonomy terms from displaying on the front end. A interesting point though—I did want to use my custom taxonomy terms to filter a Case Study gallery. Apparently, this was one spot I needed a public taxonomy. Oddly enough, I updated my functions “Public” code here from “false” to “true,” created the gallery’s filter bar, and reset it to “false.” The gallery still filters, but the terms don’t display archive pages.
function case_studies_custom_taxonomy() {
$labels = array(
'name' => _x( 'Services', 'taxonomy general name' ),
'singular_name' => _x( 'Service', 'taxonomy singular name' ),
'search_items' => __( 'Search Services' ),
'all_items' => __( 'All Services' ),
'parent_item' => __( 'Parent Service' ),
'parent_item_colon' => __( 'Parent Service:' ),
'edit_item' => __( 'Edit Service' ),
'update_item' => __( 'Update Service' ),
'add_new_item' => __( 'Add New Service' ),
'new_item_name' => __( 'New Type Name' ),
'menu_name' => __( 'Services' ),
);
register_taxonomy('services', array('case-studies'), array(
'hierarchical' => true,
'labels' => $labels,
'show_ui' => true,
'show_admin_column' => true,
'query_var' => true,
'public' => false, /*doesn't display front-end*/
'rewrite' => array( 'slug' => 'services' ),
));
}
4. Optional: Un-register the default Category taxonomy on a particular post type.
My Custom Post Type (CPT) only needs one taxonomy. That’s my custom taxonomy. So, I removed the default Category taxonomy (currently used for my blog posts) from my CPT to completely separate the two.
To remove a taxonomy, add the following line of code within the function:
unregister_taxonomy_for_object_type( 'category', 'case-studies' );
List the taxonomy to be removed in the first argument. Then, list the slug name of the post type from which that taxonomy will be removed.
5. Execute the function to actually make the custom taxonomy.
Below the function, add a new command to run the function:
add_action( 'init', 'case_studies_custom_taxonomy', 0 );
Replace ‘case_studies_custom_taxonomy’ with the function name you’re using.
And you’re done! A new custom taxonomy has been created.
Here’s my code to make a custom taxonomy in full:
// Define what WordPress must make and how to make it (i.e. the custom taxonomy)
function case_studies_custom_taxonomy() {
//Decide what to call the taxonomy
$labels = array(
'name' => _x( 'Services', 'taxonomy general name' ),
'singular_name' => _x( 'Service', 'taxonomy singular name' ),
'search_items' => __( 'Search Services' ),
'all_items' => __( 'All Services' ),
'parent_item' => __( 'Parent Service' ),
'parent_item_colon' => __( 'Parent Service:' ),
'edit_item' => __( 'Edit Service' ),
'update_item' => __( 'Update Service' ),
'add_new_item' => __( 'Add New Service' ),
'new_item_name' => __( 'New Type Name' ),
'menu_name' => __( 'Services' ),
);
// Make WordPress officially recognize the custom taxonomy and define its relationship with other WordPress parts
register_taxonomy('services', array('case-studies'), array(
'hierarchical' => true,
'labels' => $labels,
'show_ui' => true,
'show_admin_column' => true,
'query_var' => true,
'public' => false, /*doesn't display front-end*/
'rewrite' => array( 'slug' => 'services' ),
));
// Remove the default Category taxonomy from my Case Studies CPT
unregister_taxonomy_for_object_type( 'category', 'case-studies' );
}
// Make WordPress do what I told it to do in the above function
add_action( 'init', 'case_studies_custom_taxonomy', 0 );