I am trying hard to learn how to do rewrites. Not having really any understanding of regex is obviously not a good start.

I have a cpt books and a taxonomy book-publishers-series. I have written the following rewrite that will match for the book and place the taxonomy into the url, but I cannot get to taxonomy child pages because WP thinks the last part of the string is the book title.

add_rewrite_rule( '^publishing/books/(.+?)/([^/]+)/?$','index.php?book-publishers-series=$matches[1]&books=$matches[2]','top' );

What I would like is to have the following:

publishing/books/ <-- The archive page for the books cpt
publishing/books/parent <-- The archive page for the parent taxonomy term
publishing/books/parent/child <-- The archive page for the child taxonomy term
publishing/books/parent/child/book <-- The single page for the book

The tricky part is the book may not be under a child term. It might just be under a parent level term. So, I could have urls like:


So, how do I write the rewrite in a way that tells WP that the taxonomy area may be 1 or more levels deep? Yet, the last level may just be the child taxonomy term archive and not the book itself. So, I can't always say the book will be in X position in the url. For that reason, I can't do what this guy did: How to create a permalink structure with custom taxonomies and custom post types like base-name/parent-tax/child-tax/custom-post-type-name He had a set depth where his cpt single page would always be. Right now the rewrite rule I have works everywhere I have tested except for when I want to view a child-level taxonomy archive. It thinks that should be the book single page.

Below is the code for my cpt and taxonomy and the functions I am using to replace the rewrite wildcards with the taxonomy terms.

Any help in decoding this would be very much appreciated! Thanks!

$labels = array(
    "name" => "Books",
    "singular_name" => "Book",
    "menu_name" => "Books",
    "all_items" => "All Books",
    "add_new" => "Add New",
    "add_new_item" => "Add New Book",
    "edit" => "Edit",
    "edit_item" => "Edit Book",
    "new_item" => "New Book",
    "view" => "View",
    "view_item" => "View Book",
    "search_items" => "Search Books",
    "not_found" => "No Books Found",
    "not_found_in_trash" => "No Books Found in Trash",
    "parent" => "Parent Book",

$args = array(
    "labels" => $labels,
    "description" => "",
    "public" => true,
    "show_ui" => true,
    "has_archive" => true,
    "show_in_menu" => true,
    "exclude_from_search" => false,
    "capability_type" => "post",
    "map_meta_cap" => true,
    "hierarchical" => false,
    "rewrite" => array( "slug" => "publishing/books/%book-publishers-series%", "with_front" => true ),
    "query_var" => true,
    "supports" => array( "title", "revisions", "thumbnail" ),
    "taxonomies" => array( "book-publishers-series", "series" ) );
register_post_type( "books", $args );

$labels = array(
    "name" => "book-publishers-series",
    "label" => "Book Publishers/Series",
    "menu_name" => "Book Publishers/Series",
    "all_items" => "All Book Publishers/Series",
    "edit_item" => "Edit Book Publisher/Series",
    "view_item" => "View Book Publisher/Series",
    "update_item" => "Update Book Publisher/Series",
    "add_new_item" => "Add New Book Publisher/Series",
    "new_item_name" => "New Book Publisher/Series",
    "parent_item" => "Parent Book Type/Genre",
    "parent_item_colon" => "Parent Book Type/Genre:",
    "search_items" => "Search Book Publishers/Series",
    "popular_items" => "Popular Book Publishers/Series",
    "add_or_remove_items" => "Add or Remove Book Publishers/Series",
    "not_found" => "No Book Publishers/Series",

$args = array(
    "labels" => $labels,
    "hierarchical" => true,
    "label" => "Book Publishers/Series",
    "show_ui" => true,
    "query_var" => true,
    "rewrite" => array( 'slug' => 'publishing/books', 'with_front' => true true),
    "show_admin_column" => false,
register_taxonomy( "book-publishers-series", array( "books" ), $args );

function rewrite_add_taxonomy( $link, $taxonomy, $default ) {
    // Get post
    $post = get_post($post_id);
    if (!$post) return $permalink;

    // Get taxonomy terms
    $terms = wp_get_object_terms($post->ID, $taxonomy);   
    if (!is_wp_error($terms) && !empty($terms) && is_object($terms[0])) {
        $taxonomy_slug = $terms[0]->slug;
        if ($terms[0]->parent) {
            $parent_term = get_term($terms[0]->parent, $taxonomy);
            $taxonomy_slug = $parent_term->slug.'/'.$taxonomy_slug;
    } else {
        $taxonomy_slug = $default;

    return str_replace('%'.$taxonomy.'%', $taxonomy_slug, $link);

function append_query_string( $link, $post ) {

    if ( 'books' == get_post_type( $post ) ) {
        return rewrite_add_taxonomy( $link, 'book-publishers-series', 'no-category' );
    return $link;
add_filter( 'post_type_link', 'append_query_string', 10, 2 ); 
  • Maybe this question will help you. – Milo Mar 18 '15 at 19:12
  • That worked great, @Milo! Thanks! In an effort to keep things DRY, is there a good way to make that function reusable for other cpt/taxonomy combinations? I'm not sure how to make actions run again. I know the action was applied to that one function. So, how do I not run the action until I have run the function for a particular cpt/taxonomy combo? Can I add in function parameters within the add_action line? – Mattaton Mar 19 '15 at 16:44

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.