Welcome to the Treehouse Community

Want to collaborate on code errors? Have bugs you need feedback on? Looking for an extra set of eyes on your latest project? Get support with fellow developers, designers, and programmers of all backgrounds and skill levels here with the Treehouse Community! While you're at it, check out some resources Treehouse students have shared here.

Looking to learn something new?

Treehouse offers a seven day free trial for new students. Get access to thousands of hours of content and join thousands of Treehouse students and alumni in the community today.

Start your free trial

WordPress

Custom Post and Taxonomies

So I have been working on a small WordPress project of my own. But the structure of my pages or I should say the way I would ideally like to set up my structure doesn't seem to work how I would like. Though it is possible I may be confused on some part of how to make this work properly.

Ideally I would like my links to work like this:

www.mysite.com/some_custom_post_type/some_custom_taxonomy/a_single_post

but it seems the only way I can get this to work properly is if I do:

www.mysite.com/some_custom_post_type/a_single_post/

or

www.mysite.com/some_custom_taxonomy/custom_taxonomy_term/

It would seem more logical for websites to use custom post types followed by their taxonomy than having to build a massive taxonomy hierarchy to try to filter every page by.

23 Answers

Andrew Shook
Andrew Shook
31,709 Points

Ok so here is what I got. I started with the custom post type:

/**
 * create_news_post_type function. Create a News post type.
 * 
 * @access public
 * @return void
 */
function create_news_post_type() {
    $labels = array(
        'name'               => _x( 'News', 'post type general name' ),
        'singular_name'      => _x( 'News', 'post type singular name' ),
        'menu_name'          => _x( 'News', 'admin menu' ),
        'name_admin_bar'     => _x( 'News', 'add new on admin bar' ),
        'add_new'            => _x( 'Add New', 'News' ),
        'add_new_item'       => __( 'Add New News' ),
        'new_item'           => __( 'New News' ),
        'edit_item'          => __( 'Edit News' ),
        'view_item'          => __( 'View News' ),
        'all_items'          => __( 'All News' ),
        'search_items'       => __( 'Search News' ),
        'parent_item_colon'  => __( 'Parent News:' ),
        'not_found'          => __( 'No news found.' ),
        'not_found_in_trash' => __( 'No news found in Trash.' ),
    );

    $args = array(
        'labels'             => $labels,
        'public'             => true,
        'publicly_queryable' => true,
        'show_ui'            => true,
        'show_in_menu'       => true,
        'query_var'          => true,
        'rewrite'            => array( 'slug' => 'news' ),
        'capability_type'    => 'post',
        'has_archive'        => true,
        'hierarchical'       => false,
        'menu_position'      => null,
        'supports'           => array( 'title', 'editor', 'author', 'thumbnail', 'excerpt', 'comments' )
    );

    register_post_type( 'news', $args );
}
add_action( 'init', 'create_news_post_type' );

Its pretty basic, and will set up an archive page at www.mysite.com/news. Pay special notice to the slug, I called it news, and this will be important in the next. Here is the code I used to set up the custom taxonomy:

/**
 * create_news_type_taxonomies function. Creates Type of News Taxonomy for taggin news.
 * 
 * @access public
 * @return void
 */
function create_news_type_taxonomies() {
    // Add new taxonomy, make it hierarchical (like categories)
    $labels = array(
        'name'              => _x( 'Types of News', 'taxonomy general name' ),
        'singular_name'     => _x( 'Types of New', 'taxonomy singular name' ),
        'search_items'      => __( 'Search Type of News' ),
        'all_items'         => __( 'All Types of News' ),
        'parent_item'       => __( 'Parent Types of News' ),
        'parent_item_colon' => __( 'Parent Types of News:' ),
        'edit_item'         => __( 'Edit Type of News' ),
        'update_item'       => __( 'Update Type of News' ),
        'add_new_item'      => __( 'Add New Type of News' ),
        'new_item_name'     => __( 'New Type of News Name' ),
        'menu_name'         => __( 'Type of News' ),
    );

    $args = array(
        'hierarchical'      => true,
        'labels'            => $labels,
        'show_ui'           => true,
        'show_admin_column' => true,
        'query_var'         => true,
        'rewrite'           => array( 'slug' => 'news' ),
    );

    register_taxonomy( 'news_type', array( 'news' ), $args );
}//*/
add_action( 'init', 'create_news_type_taxonomies', 0 );

This is also pretty standard, but I did a little trick with the slug. I gave it the same slug the custom post type. That way I don't need a rewrite rule for the taxonomy, since taxonomies append the slug to the being of the url like this "/slug/taxonomy_name". So now we have set up each taxonomy word so that it will show up at www.mysite.com/news/taxonomy-word/ and use category template.

Next I wrote a function that changes the way permalinks are saved in the post edit form so that news post will be saved with a url like www.mysite.com/news/new_type/post-slug. This function uses the post_type_link hook.

/**
 * rewrite_news_post_links function. Adds rule to rewrite post permalinks when saved.
 * 
 * @access public
 * @param mixed $post_link
 * @param int $id (default: 0)
 * @return void
 */
function rewrite_news_post_links( $post_link, $id = 0 ) {

    $post = get_post($id);
     // checks to see that there is a post and that it is a "news" post type.
    if ( is_wp_error($post) || 'news' != $post->post_type || empty($post->post_name) )
        return $post_link;

    // Get the news_type taxonomy associated with the post:
    $terms = get_the_terms($post->ID, 'news_type');

    if( is_wp_error($terms) || !$terms ) {
         //if you change the initial taxonomy to all you can replace 'uncategorize'd with "all"
         $news_type = 'uncategorized';
    }
    else {
        $news_type_obj = array_pop($terms);
        // if news post type has news_type taxonomy get the taxonomy slug
        $news_type = $news_type_obj->slug;
    }
    // returns permalink in the form of /news/news-type-taxonomy-word/post-slug
    return home_url(user_trailingslashit( "news/$news_type/$post->post_name" ));
}
add_filter( 'post_type_link', 'rewrite_news_post_links' , 10, 2 );//*/

Finally, I created a rewrite rule that will allow wordpress to parse a url with the structure www.mysite.com/news/news_type/post-slug and return a post of the news post type with a matching taxonomy word and post slug.

/**
 * rewrite_rules function.
 * 
 * @access public
 * @return void
 */
function rewrite_rules() {
    //adds rewrite rule so wordpress will recognize /news/anything/anything
    add_rewrite_rule("^news/([^/]+)/([^/]+)/?",'index.php?post_type=news&news_type=$matches[1]&news=$matches[2]','top');
}
add_action('init', 'rewrite_rules');
Andrew Shook
Andrew Shook
31,709 Points

I made some edits because for typos.

Andrew Shook
Andrew Shook
31,709 Points

Chris Howell, You need to use Wordpress's Rewrite API. You can find a good tutorial here and here. The second one has a great example for exactly what you want to do.

I guess I should have also mentioned I am using Custom Post Type UI Plugin. I believe they have incorporated the flush rewrite rules as necessary. So I am now under the impression all I need to do is basically write my own function for a rewrite rule i would like and throw it in my functions.php file add the proper hook and theoretically it would work fine with the CPT UI ?

Or do you foresee possible conflicts occurring?

Andrew Shook
Andrew Shook
31,709 Points

No idea. Without knowing more about how you have everything setup it would be a wild guess. I suggest giving it a shot and see if it breaks anything. If it does then debug. Plans are always good, but just like battle plans they never survive first contact.

I did a quick skim over the content, thank you very much. I appreciate, I haven't heard of the Rewrite API, though it does look like what I am seeking. The linked pages you posted look pretty in-depth so I will have to respond with how it works out a bit later when I am able to try it out.

Again, appreciate the response.

Andrew Shook
Andrew Shook
31,709 Points

No worries. If you need help let me know. I felt like it was to robust topic to explain everything here in the forum.

Well I gave it a fair shot these past few days. I followed the tutorials the best I could but it does not seem to work for what I am doing, very likely I am doing it wrong. I'll have to research further to get a better understanding of the other aspects of how all this pulls together. Or maybe Zac Gordon will throw something together for us in the Workshops or make an advanced course on this topic? ;)

Andrew Shook
Andrew Shook
31,709 Points

Chris, if you feel comfortable zipping up your WP installation and database I don't mind talking a look at it. Just let me know and you could dropbox it or something.

Oh right now I am using a fresh install of the newest WordPress, I have it installed on one of my other local machines just so I can intentionally break this thing as much as I'd like. I have been searching the fine inter-webs and I think I found a post that led me to multiple other posts, which may have explained part of the problem I am not understanding. I went and made a nice big cup of coffee and put on some coding music. I feel I am on the verge of understanding this, though If I fail miserably. I will put my WinRAR to work. ;)

I installed a "Rewrite Analyzer Plugin" and finally have the custom post type showing up at the top under the patterns and the custom taxonomy with the rewrite regex showing on the substitution. Which It wasn't before.... so I am calling that progress, for now.

Ok, so let us pretend that I successfully created this Custom Post Type and a Custom Taxonomy using the Rewrite API.

How exactly does the template hierarchy work with this new Rewrite change?

Typing out the address with the custom post type appended to the end pulls a 404.

Typing out the address with the custom post type AND the custom taxonomy appended to the end pulls the 404 page.

But then Typing out the address with custom post type AND custom taxonomy AND the single post assigned to that custom post type DOES NOT pull the 404 but instead pulls nothing(like blank info) none of the default hierarchy pages, this to me shows it is trying to DO something.

Hmmmm....

Andrew Shook
Andrew Shook
31,709 Points

In theory, just custom post type in the url should give you a list of all those post types, and adding a tag you limit that post type to just the ones containing that tag. Then adding post title should give just a single post matching all the criteria and has that title specified. It that what you are trying to do?

So just to be sure I am not missing any of the hierarchy pages, i threw my rewrite functions into the twentyfourteen theme instead of my custom theme.

www.mysite.com/custom_post_type/ Pulls the 404 page or "Not Found".

www.mysite.com/custom_post_type/custom_taxonomy/ Pull the 404 page as well.

www.mysite.com/custom_post_type/custom_taxonomy/slug-of-single-post/ Pulls the archive.php and loads the post title and content. The URL is not redirecting or anything it remains exactly how I typed it.

Andrew Shook
Andrew Shook
31,709 Points

OK, check and make sure that the custom post type property, has_archive, is set to true. This should make it so that www.mysite.com/custom_post_type/ will show a list of all posts of that post type and it will use archive.php template. For the custom taxonomy, you will need to add a rewrite rule above the post/tag/slug that tells wordpress to return a list of post where post_type=custome_post_type && tag=custom_taxonomy.

Ahh, good call. I have recreated this post type so many times now forgot to set the value back to true. CPT UI has it false at default. Now the custom_post_type appended to the end of the URL is bringing in the posts. Awesome. Now when I create an Archive-customposttype.php file it should work out.

Now to work on this second rewrite rule you speak of.

Thank you good sir.

Andrew Shook
Andrew Shook
31,709 Points

No problem, I'm glad I could help.

Ah, that 2nd rewrite was easier. It is working now.

You sir have been a god send on this fabulous day.

Thank you very much. ;)

Andrew Shook
Andrew Shook
31,709 Points

I'm glad I come help and point you in the right direction

Okay so I have this thing working mostly how I want now. Fairly grasping the whole concept of this Rewrite API now.

I am still having just one problem and Im unsure what is causing it.

www.mysite.com/custom-post-type/ will 404 even with has_archive set to true.

www.mysite.com/custom-post-type/custom_taxonomy/ works fine and loads the taxonomy.php file.

www.mysite.com/custom-post-type/custom_taxonomy/single-post/ works fine and loads the single.php file.

What could be causing the custom-post-type page not to load the archive.php file? since the rest seem to be loading just fine?

I have tried fiddling around with each setting one by one and then each time i flush the rewrite rules just to be safe. But I am having no luck.

I can however get the custom-post-type to load the archive.php page, but then the custom_taxonomy wont work in the URL.

My custom post type and custom taxonomy are set up kind of like this, obviously they will be using other terms and I removed a bunch of the extra labels and what not just to show the important arguments.

register_post_type('mycpt', array(
    'label' => 'My CPTs',
    'public' => true,
    'capability_type' => 'post',
    'hierarchical' => false,
    'rewrite' => array(
      'slug' => 'new-slug/%mytax%',
      'with_front' => 0
      ),
    'query_var' => true,
    'has_archive' => true,
      ),
    'labels' => array (
      'name' => 'My CPTs',
      'singular_name' => 'My CPT',
      )
    ) 
  );

register_taxonomy( 'mytax', 'mycpt', array( 
    'label' => 'MyTax',
    'show_ui' => true,
    'rewrite' => array( 'slug' => 'new-slug' ),
      )
    ) 
  );
Andrew Shook
Andrew Shook
31,709 Points

Can you post your rewrite rules?

The way I have the rewrite slugs set up inside the post types, are working the same way with rewrite rules as they are now without them. So I basically removed them for the time being.

So right now without the rewrite rules written in, getting the permalinks arent going to work if I was to try because it would return the %mytax% inside the url.

so everything works fine just after the custom post type just as it is there, without any specific rewrite rules written in. So I only really need a rewrite rule for the custom post type?

I was attempting it this way after watching this video and reviewing his slides.

I am going to wipe my local server clean and re-install fresh wordpress and fresh database and just test this out with 1 custom post type and 1 custom taxonomy, because removing the rewrite rules and then having this still redirecting me to the right pages confused the heck out of me. haha.

Andrew Shook
Andrew Shook
31,709 Points

Chris, I have a working piece of code. Please tell me the name of your custom post type and custom taxonomy and I'll post it of you.

Ahhhhhhhhhhhhhhh! I think I figured it out.

The way I have it set up when registering the post types works fine. You have to have 1 rewrite for the custom-post-type. Because if you use the rewrite analyzer and watch how wordpress is trying to figure it out it looks for pagename instead of post_type.

So using the custompost type and taxonomy from above... the one rewrite I wrote that finished and fixed it was this.

add_rewrite_rule('new-slug/?$','index.php?post_type=mycpt','top'); 

now when I type:

http://localhost/new-slug/

it pulls the archive of posts and I can make it list everything from that post type.

then

http://localhost/new-slug/any-taxonomy/ 

it pulls only the posts from the custom post type with whichever taxonomy is there and uses the taxonomy.php templates.

and then

http://localhost/new-slug/any-taxonomy/a-post-name/ 

will pull in just that 1 post from that specific taxonomy within that custom post type and it will use the single.php template by default.

AWESOME! :-)

Thank you again, for sticking through this with me. Appreciate the help.

Andrew Shook
Andrew Shook
31,709 Points

Chris, I have a working piece of code, could you tell me the names of your custom post type and taxonomy so I can just paste it in here for you?

I just made up the generic sample ones above so anyone could follow. If we were to stick with those.

Custom Post Type would be: mycpt

Custom Taxonomy would be: mytax

Ahhh, nicely done sir.

I have mine working as well now with the permalinks showing properly.

So there is basically two ways to get to the same solution, with a few minor differences. :)

Andrew Shook
Andrew Shook
31,709 Points

I'd be interested to see your code.

Registering my custom post type and taxonomy

function wp_make_tax(){
register_taxonomy( 'make', 'model', array( 
    'hierarchical' => true,
    'label' => 'Makes',
    'show_ui' => true,
    'query_var' => true,
    'rewrite' => array( 'slug' => 'vehicles' )
    )
  ) 
);
}
add_action('init','wp_make_tax');

function wp_car_post(){
register_post_type('car', array(
    'label' => 'Cars',
    'description' => '',
    'public' => true,
    'show_ui' => true,
    'show_in_menu' => true,
    'capability_type' => 'post',
    'map_meta_cap' => true,
    'hierarchical' => false,
    'rewrite' => array(
      'slug' => 'vehicles/%make%',
      'with_front' => 0
      ),
    'query_var' => true,
    'has_archive' => true
    )
  )
);
}
add_action('init','wp_car_post');

The rewrite rule and rewrite tag I add with it.

function wp_register_rewrite_rules(){
add_rewrite_rule('vehicles/?$','index.php?post_type=car','top');
add_rewrite_tag('%make%','([^/]+)');
}
add_action( 'init', 'wp_register_rewrite_rules' );

and the post type link filter

function car_post_link( $post_link, $id = 0 ) {
    $post = get_post($id);
    if ( is_wp_error($post) || 'car' != $post->post_type || empty($post->post_name) )
        return $post_link;
    // Get the genre:
    $terms = get_the_terms($post->ID, 'make');
    if( is_wp_error($terms) || !$terms ) {
        $makes = 'uncategorized';
    }
    else {
        $make_obj = array_pop($terms);
        $makes = $make_obj->slug;
    }
    return home_url(user_trailingslashit( "vehicles/{$makes}/{$post->post_name}" ));
}
add_filter( 'post_type_link', 'car_post_link' , 10, 2 );

I deleted some of the excess arguments from the post type and taxonomy, so hopefully there arent too many typos. lol

This should build the URLs basically the same way.

I am going to rebuild my post and taxonomy using your method right now, Id like to see how the rewrite analyzer reads it and see what differences or if it has any differences.

If there was a difference in the Rewrite Analyzer it was very small, it still read the patterns and substitutions the same way. Both ways seem to work for me. :)