I am trying to set up a multi-level custom post type structure with permalinks that look like authors/books/chapters
, with authors, books, and chapters all set up as their own custom post type. For example, a typical URL on this site might look like example.com/authors/stephen-king/the-shining/chapter-3/
Each chapter can only belong to one book, and each book can only belong to one author. I’ve considered using taxonomies instead of CPTs for authors and books, but I need to associate metadata with each item and I prefer the post interface for this.
I’m most of the way there by simply setting up each custom post as a child of an entry in the CPT one level up. For example, I create “Chapter 3” and assign “The Shining” as a parent using a custom meta-box. “The Shining” in turn has “Stephen King” as a parent. I haven’t had any trouble creating these relationships.
I’m using rewrite tags in the CPT slugs and the permalinks want to work, but they’re not quite right. Using a re-write analyzer, I can see that the rewrite rules are actually generated, but they don’t seem to be in the right order and so other rules are processed first.
Here’s how I’ve registered my CPTs:
function cpt_init() {
$labels = array(
'name' => 'Authors'
);
$args = array(
'labels' => $labels,
'public' => true,
'publicly_queryable' => true,
'show_ui' => true,
'show_in_menu' => true,
'query_var' => true,
'rewrite' => array(
'slug' => 'author',
'with_front' => FALSE,
),
'with_front' => false,
'capability_type' => 'post',
'has_archive' => false,
'hierarchical' => true,
'menu_position' => null,
'supports' => array( 'title', 'editor' )
);
register_post_type('authors',$args);
$labels = array(
'name' => 'Books'
);
$args = array(
'labels' => $labels,
'public' => true,
'publicly_queryable' => true,
'show_ui' => true,
'show_in_menu' => true,
'query_var' => true,
'rewrite' => array(
'slug' => 'author/%authors%',
'with_front' => FALSE,
),
'with_front' => false,
'capability_type' => 'post',
'has_archive' => false,
'hierarchical' => true,
'menu_position' => null,
'supports' => array( 'title', 'editor' )
);
register_post_type('books',$args);
$labels = array(
'name' => 'Chapters'
);
$args = array(
'labels' => $labels,
'public' => true,
'publicly_queryable' => true,
'show_ui' => true,
'show_in_menu' => true,
'query_var' => true,
'rewrite' => array(
'slug' => 'author/%authors%/%books%',
'with_front' => FALSE,
),
'with_front' => FALSE,
'capability_type' => 'post',
'has_archive' => false,
'hierarchical' => true,
'menu_position' => null,
'supports' => array( 'title', 'editor' )
);
register_post_type('chapters',$args);
}
add_action( 'init', 'cpt_init' );
So is there any way to change the priority of my rewrite rules so that authors, books, and chapters are all matched first?
I also know that I’m going to have to add a post_type_link
filter, but that seems secondary to getting the permalinks right in the first place. If anyone knows where I can find a comprehensive overview of how that filter works, it would be appreciated.
If you want to keep ‘authors’ as the base slug in the permalinks, i.e. example.com/authors/stephen-king/ for the ‘authors’ CPT, example.com/authors/stephen-king/the-shining/ for the ‘books’ CPT and example.com/authors/stephen-king/the-shining/chapter-3/ for the ‘chapters’ CPT, WordPress will think pretty much everything is an ‘authors’ post or a hierarchical child of an ‘authors’ post and, since that is not the case, WordPress ultimately becomes very confused.
With that said, there’s a workaround that is quite basic but as long as your permalink structure always follows the same order, i.e. the word ‘authors’ is always followed by an author slug, which is always followed by a book slug which is always followed by a chapter slug, then you should be good to go.
In this solution, there’s no need to define the rewrite slug in the custom post type definition for ‘chapters’ and ‘books’, but set the ‘authors’ rewrite slug as simply ‘authors’, place the following code in your functions.php file and “flush” your rewrite rules.
Learn more about the CPT-onomies plugin
I don’t have personal experience with such a scenario, but Randy Hoyt did a presentation at WordCamp San Fran last weekend about “Subordinate Post Types” that sounds like what you’re talking about.
Here’s his page for the talk that includes his presentation slides and links to a plugin he built for working with subordinate post types:
http://randyhoyt.com/wordpress/subordinate-post-types/
The rules will get added to the extra_rules_top of WP_Rewrite in the order that the extra permastructs are added. So, switching the order that you register the post types will switch the order of the rewrite rules get generated making the chapter rewrite get matched first. However, since you’re using the query_var from the other post_types the wp_query may end up matching one of those as the queried post name before matching the chapter like you want.
I would create new rewrite tags to represent the placeholders for the parent author and parent-book, ie:
When doing this, you’ll have to filter ‘query_vars’ to make ‘parent_book’ public. Then you’ll need to add a filter to pre_get_posts that will convert the name set as the parent_book query_var into the post_id and set it as the ‘post_parent’.