I have created a custom post type called "portfolio" and I use a static page to list my portfolio posts because I need to add content using the content editor.

My problem is that I would like this structure:

http://www.example.com/portfolio/ (It is a static page)

http://www.example.com/portfolio/photography (taxonomy term)

http://www.example.com/portfolio/photography/post-title (single post type)

Is it posible?

How can I do it?

  • Can you add the code your using to register the post types/taxomonies etc
    – Welcher
    Oct 15, 2014 at 0:28

2 Answers 2


Yes, it is possible, and it is done by creating the custom taxonomy, then creating the rewrite in the custom post type, and then adding a filter for post_type_link to edit the actual permalink. Once you have the code in place, you have to go Settings > Permalinks and hit submit to refresh everything.

add_action( 'init', 'register_custax_type', 0 );
function register_custax_type() {
    // Add new hierarchical taxonomy
    $labels = array(
        'name'              => _x( 'Types', 'taxonomy general name' ),
        'singular_name'     => _x( 'Type', 'taxonomy singular name' ),
        'search_items'      => __( 'Search Types' ),
        'all_items'         => __( 'All Types' ),
        'parent_item'       => __( 'Parent Type' ),
        'parent_item_colon' => __( 'Parent Type:' ),
        'edit_item'         => __( 'Edit Type' ),
        'update_item'       => __( 'Update Type' ),
        'add_new_item'      => __( 'Add New Type' ),
        'new_item_name'     => __( 'New Type Name' ),
        'menu_name'         => __( 'Type' ),

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

    register_taxonomy( 'type', array( 'portfolio' ), $args );

add_action( 'init', 'register_cpt_portfolio' );
function register_cpt_portfolio() {

    $labels = array( 
        'name'                  => _x( 'Portfolios', 'portfolio' ),
        'singular_name'         => _x( 'Portfolio', 'portfolio' ),
        'add_new'               => _x( 'Add New', 'portfolio' ),
        'add_new_item'          => _x( 'Add New Portfolio', 'portfolio' ),
        'edit_item'             => _x( 'Edit Portfolio', 'portfolio' ),
        'new_item'              => _x( 'New Portfolio', 'portfolio' ),
        'view_item'             => _x( 'View Portfolio', 'portfolio' ),
        'search_items'          => _x( 'Search Portfolios', 'portfolio' ),
        'not_found'             => _x( 'No Portfolios found', 'portfolio' ),
        'not_found_in_trash'    => _x( 'No Portfolios found in Trash', 'portfolio' ),
        'parent_item_colon'     => _x( 'Parent Portfolio:', 'portfolio' ),
        'menu_name'             => _x( 'Portfolios', 'portfolio' ),

    $args = array( 
        'labels'                => $labels,
        'hierarchical'          => false,
        'description'           => 'Portfolios',
        'supports'              => array( 'title', 'revisions', 'editor', 'thumbnail' ),
        'public'                => true,
        'show_ui'               => true,
        'show_in_menu'          => true,
        'menu_position'         => 25,
        'menu_icon'             => 'dashicons-images-alt',
        'show_in_nav_menus'     => true,
        'publicly_queryable'    => true,
        'exclude_from_search'   => true,
        'has_archive'           => false,
        'query_var'             => true,
        'can_export'            => true,
        'capability_type'       => 'page',
        'taxonomies'            => array( 'type' ),
        'rewrite'               => array(
                                'slug'          => 'portfolio/%type%',
                                'with_front'    => false

    register_post_type( 'portfolio', $args );

add_filter( 'post_type_link', 'portfolio_permastruct', 10, 2 );
function portfolio_permastruct( $link, $post ) {
    // Only update portfolio permalinks
    if( $post->post_type !== 'portfolio')
        return $link;

    // get terms, if none are returned, it's false and just returns
    if( $cats = get_the_terms( $post->ID, 'type' ) )
        $link = str_replace( '%type%', array_pop( $cats )->slug, $link );
    return $link;

A full walk through can be found here

  • Thanks for your answer but I'm using a static page to list my portfolio posts, so when I register de CPT I put archive to false. Oct 15, 2014 at 13:48
  • I just updated my answer with 'has_archive' => false, still works without issues in my dev environment. Oct 15, 2014 at 15:58
  • Great, it works.The problem now is that if I use: get_term_link, the links are like this example: "example.com/type/photography" when I would like: "example.com/portfolio/photography" Oct 15, 2014 at 17:09
  • Just edited again, added 'rewrite' => array( 'slug' => 'portfolio' ) at the end of the args for the taxonomy, that should do it. Oct 16, 2014 at 6:03
  • Perfect. It works like a charm :) Thank you very much. Oct 16, 2014 at 18:10

This might serve as a work-around. Try changing the name of your post-type from "Portfolio" to something like "Work" or "Examples." It just needs to be a word that makes sense to you.

Then change the name of your taxonomy from "Type" to "Portfolio."

I think WordPress defaults to showing the relationship of the term to the taxonomy because there could be examples where you are using the same term in different taxonomies.

Imagine for example a site listing book reviews. The custom post-type could be "books"

You could also have taxonomies such as "Author-last-name, genre, language, etc."

If you had a book about Vampires by an author named Jim English, you would use the term English in both the Author-last-name and language taxonomies. In this case http://www.example.com/author-last-name/English would list all authors named English while http://www.example.com/language/English would list all books written in English.

If you could create something like http://www.example.com/books/English it would be confusing. In theory that would be all books in English and all books by authors named English and this would probably need a special query.

I realize this example is a tad convoluted, but I think this is why WordPress is behaving the way it is.

  • Thank you very much for your explanation. Now I understand better why WordPress works in this way. Oct 16, 2014 at 18:12

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.