Custom post types, taxonomies, and permalinks

This is driving me nuts and I’m sure it’s simple but nothing I search for comes up with a simple structure (everything is very complex).

I have a custom post type product_listing and a custom taxonomy of product_cat (which is hierarchical and should be have like categories).

Read More

I simply want my URLs to look like this:

mysite.com/products/category1/product-name1 
mysite.com/products/category2/product-name2

But for the life of me, no matter what I do, I’m getting the dreaded 404 issue. Pages work okay and Posts work okay but my custom posts don’t work correctly. They’re showing up as:

mysite.com/products/product-name1
mysite.com/products/product-name2

Which actually works! It’s just that I want to see my custom taxonomy in there plus I want to be able to access the taxonomy.php template I have setup by going to:

mysite.com/products/category1/
mysite.com/products/category2/

None of my slugs are the same, nor do I want them to be. Here is the post type and taxonomy part of my functions.php file:

///// CUSTOM POST TYPES /////

// register the new post type
register_post_type( 'product_listing', array( 
    'labels'                 => array(
        'name'               => __( 'Products' ),
        'singular_name'      => __( 'Product' ),
        'add_new'            => __( 'Add New' ),
        'add_new_item'       => __( 'Create New Product' ),
        'edit'               => __( 'Edit' ),
        'edit_item'          => __( 'Edit Product' ),
        'new_item'           => __( 'New Product' ),
        'view'               => __( 'View Products' ),
        'view_item'          => __( 'View Product' ),
        'search_items'       => __( 'Search Products' ),
        'not_found'          => __( 'No products found' ),
        'not_found_in_trash' => __( 'No products found in trash' ),
        'parent'             => __( 'Parent Product' ),
    ),
    'description'           => __( 'This is where you can create new products on your site.' ),
    'public'                => true,
    'show_ui'               => true,
    'capability_type'       => 'post',
    'publicly_queryable'    => true,
    'exclude_from_search'   => false,
    'menu_position'         => 2,
    'menu_icon'             => get_stylesheet_directory_uri() . '/images/tag_orange.png',
    'hierarchical'          => true,
    '_builtin'              => false, // It's a custom post type, not built in!
    'rewrite'               => array( 'slug' => 'products', 'with_front' => true ),
    'query_var'             => true,
    'supports'              => array( 'title', 'editor', 'author', 'thumbnail', 'excerpt', 'trackbacks', 'custom-fields', 'comments', 'revisions' ),
) );


//hook into the init action and call create_book_taxonomies when it fires
add_action( 'init', 'create_product_taxonomies', 0 );
//add_action('admin_init', 'flush_rewrite_rules');

//create two taxonomies, genres and writers for the post type "book"
function create_product_taxonomies() {
    // Add new taxonomy, make it hierarchical (like categories)
    $labels = array(
        'name'              => _x( 'Categories', 'taxonomy general name' ),
        'singular_name'     => _x( 'Category', 'taxonomy singular name' ),
        'search_items'      =>  __( 'Search Categories' ),
        'all_items'         => __( 'All Categories' ),
        'parent_item'       => __( 'Parent Categories' ),
        'parent_item_colon' => __( 'Parent Categories:' ),
        'edit_item'         => __( 'Edit Category' ), 
        'update_item'       => __( 'Update Category' ),
        'add_new_item'      => __( 'Add New Category' ),
        'new_item_name'     => __( 'New Category Name' ),
        'menu_name'         => __( 'Category' ),
    );  

    register_taxonomy( 'product_cat', array( 'product_listing' ), array(
        'hierarchical'  => true,
        'labels'        => $labels,
        'show_ui'       => true,
        'query_var'     => true,
        //'rewrite'     => true,
        'rewrite'       => array( 'slug' => '%category%', 'with_front' => true ),
    ) );

    // Add new taxonomy, NOT hierarchical (like tags)
    $labels = array(
        'name'                       => _x( 'Scents', 'taxonomy general name' ),
        'singular_name'              => _x( 'Scent', 'taxonomy singular name' ),
        'search_items'               =>  __( 'Search Scents' ),
        'popular_items'              => __( 'Popular Scents' ),
        'all_items'                  => __( 'All Scents' ),
        'parent_item'                => null,
        'parent_item_colon'          => null,
        'edit_item'                  => __( 'Edit Scent' ), 
        'update_item'                => __( 'Update Scent' ),
        'add_new_item'               => __( 'Add New Scent' ),
        'new_item_name'              => __( 'New Scent Name' ),
        'separate_items_with_commas' => __( 'Separate scents with commas' ),
        'add_or_remove_items'        => __( 'Add or remove scents' ),
        'choose_from_most_used'      => __( 'Choose from the most used scents' ),
        'menu_name'                  => __( 'Scents' ),
    ); 

    register_taxonomy( 'scent', 'product_listing', array(
        'hierarchical'  => false,
        'labels'        => $labels,
        'show_ui'       => true,
        'query_var'     => true,
        //'rewrite'     => array( 'slug' => 'scents' ),
    ) );
}

I also have another custom taxonomy of scents that I’d ideally like to have some kind of friendly url but I’m more open on this. I’d like to maybe access a list of all scents by going to mysite.com/products/scents but they don’t have to be category specific.

Can anybody help me?

Related posts

Leave a Reply

5 comments

  1. Change slug in your post type arguments to products/%product_cat%, and slug in your taxonomy arguments to just products, then flush your rewrite rules. WordPress should now handle /products/my-product-cat/post-name/!

    Now finally, we need to help WordPress a little with generating permalinks (out of the box, it won’t recognise the permastruct tag %product_cat%):

    /**
     * Inject term slug into custom post type permastruct.
     * 
     * @link   http://wordpress.stackexchange.com/a/5313/1685
     * 
     * @param  string  $link
     * @param  WP_Post $post 
     * @return array
     */
    function wpse_5308_post_type_link( $link, $post ) {
        if ( $post->post_type === 'product_listing' ) {
            if ( $terms = get_the_terms( $post->ID, 'product_cat' ) )
                $link = str_replace( '%product_cat%', current( $terms )->slug, $link );
        }
    
        return $link;
    }
    
    add_filter( 'post_type_link', 'wpse_5308_post_type_link', 10, 2 );
    

    One thing to note, this will just grab the first product category for the post ordered by name. If you’re assigning multiple categories to a single product, I can easily change how it determines which one to use in the permalink.

    Lemme know how you get on with this, and we can tackle the other issues!

  2. Thanks @TheDeadMechanic, your answer helped me out, but only partially. I wanted to do the same thing @RodeoRamsey asked for, but with nested categories (ie: mysite.com/products/category1/child-category-1/grandchild-category-1/product-name) and your solution didn’t work for that.

    I finally came up with an extended solution to my question that works, so if anyone else needs nested categories/subcategories you can see a detailed solution on my own question. Hope it helps others, and thanks for the initial steps.

  3. I’m not sure wp supports that structure out of the box – but you can very easily create your own rewrite rules to do so.

    Check out a previous answer here Author url rewrite.

    You can change the line

    $newrules['author/([^/]+)/songs/?$'] = 'index.php?post_type=songs&author=$matches[1]';
    

    to something like

    $newrules['products/([^/]+)/([^/]+)/?$'] = 'index.php?post_type=product_listing&product_cat=$matches[1]&name=$matches[2]';
    

    the product_cat part here may be superfluous – I am not sure if it is needed.

    You can add any rules you like and they will have priority over the inbuilt ones.

  4. Actually, it’s pretty easy. You just need one line. Here’s my code

    function create_product_taxonomies()
    {
    // Add new taxonomy, make it hierarchical (like categories)
        $labels = array(
            'name' => _x('Categories', 'taxonomy general name'),
            'singular_name' => _x('Category', 'taxonomy singular name'),
            'search_items' => __('Search Categories'),
            'all_items' => __('All Categories'),
            'parent_item' => __('Parent Categories'),
            'parent_item_colon' => __('Parent Categories:'),
            'edit_item' => __('Edit Category'),
            'update_item' => __('Update Category'),
            'add_new_item' => __('Add New Category'),
            'new_item_name' => __('New Category Name'),
            'menu_name' => __('Category'),
        );
    
        register_taxonomy('product_cat', array('product_listing'), array(
            'hierarchical' => true,
            'labels' => $labels,
            'show_ui' => true,
            'query_var' => true,
            'rewrite' => array('hierarchical' => true),
        ));
    

    And applied to the generated taxonomy for my Reviews CPT from GenerateWP.com. I’m using this on my own WordPress site, https://www.wpstarters.com

    function reviews_category_taxonomy() {
    
        $labels = array(
            'name'                       => _x( 'Reviews Categories', 'Taxonomy General Name', 'reviews_category' ),
            'singular_name'              => _x( 'Reviews Category', 'Taxonomy Singular Name', 'reviews_category' ),
            'menu_name'                  => __( 'Reviews Category', 'reviews_category' ),
            'all_items'                  => __( 'All Review Categories', 'reviews_category' ),
            'parent_item'                => __( 'Parent Review Category', 'reviews_category' ),
            'parent_item_colon'          => __( 'Parent Review Category:', 'reviews_category' ),
            'new_item_name'              => __( 'New Review Category Name', 'reviews_category' ),
            'add_new_item'               => __( 'Add New Review Category', 'reviews_category' ),
            'edit_item'                  => __( 'Edit Review Category', 'reviews_category' ),
            'update_item'                => __( 'Update Review Category', 'reviews_category' ),
            'view_item'                  => __( 'View Review Category', 'reviews_category' ),
            'separate_items_with_commas' => __( 'Separate items with commas', 'reviews_category' ),
            'add_or_remove_items'        => __( 'Add or remove items', 'reviews_category' ),
            'choose_from_most_used'      => __( 'Choose from the most used', 'reviews_category' ),
            'popular_items'              => __( 'Popular Review Categories', 'reviews_category' ),
            'search_items'               => __( 'Search Items', 'reviews_category' ),
            'not_found'                  => __( 'Not Found', 'reviews_category' ),
            'no_terms'                   => __( 'No Review Categories', 'reviews_category' ),
            'items_list'                 => __( 'Review Categories list', 'reviews_category' ),
            'items_list_navigation'      => __( 'Review Categories list navigation', 'reviews_category' ),
        );
        $args = array(
            'labels'                     => $labels,
            'hierarchical'               => true,
            'public'                     => true,
            'show_ui'                    => true,
            'show_admin_column'          => true,
            'show_in_nav_menus'          => true,
            'show_tagcloud'              => false,
            'show_in_rest'               => true,
            'rewrite' => array( 'hierarchical' => true ),
        );
        register_taxonomy( 'reviews_category', array( 'wps_reviews' ), $args );
    
    }
    add_action( 'init', 'reviews_category_taxonomy', 0 );
    

    All you need it so put the ‘rewrite’ => array(‘hierarchical’ => true),