Custom Permalinks for hierarchical taxonomies

The title is pretty vast and non-descriptive, but it sets the general idea of the post. In general the Permalinks system that WordPress uses is pretty flexible, but it also has it’s limitations. Quite recently as I was working on a client’s project, I stumbled upon one of them.

The project was to create a Frequently Asked Questions database, using WordPress. The client wanted the following structure for the database(this is a simplified version)::

  • Index of the database ( ) [Index of the whole database]
    • Main Category ( ) [Index of the Main Category]
      • Post in the Main Category ( ) [View only Post 1]
      • Sub-Category ( ) [Index of the Sub-Category]
        • Post in the Sub-Category ( ) [View only Post 2]
    • Another Main Category ( ) [Index of the other Main Category=]

Everything looks normal(as structure of the URL’s) for the regular user, but WordPress has a different idea on the matter Instead of an URL like we will be able to see the index of the Main Category at an URL like, which as you can notice is quite different from the initial idea. To fix this issue we have to add a couple of custom Rewrite Rules and functions.

The first step is to register the new Custom Post Type and it’s Custom Taxonomy. Here’s how:

This code registers the new post type and it’s “FAQ Categories” taxonomy, which we will use to categorize the questions.

After we register the new post type, we have to add two functions that will make the Permalinks work the way we want them to:

In the code above we registered two functions – register_faq_rewrite_rules() and fix_faq_subcategory_query().

The first function adds the following rewrite rules:

  • faq/([^/]+)/?$ – this rule will be applied when the URL of the page that is being loaded looks something like “faq/any-character/” – in other words when the URL starts(after the part with the home page URL) with “faq/” followed by any combination of symbols, without “/”( “([^/]+)” ), and possibly followed by a “/”( “/?” ). Also in order to match, this part has to be the last part of the URL(in other words there won’t be a match, if the URL is “faq/any-character/something”).When the current URL matches, the page will display a FAQ Category archive for the category(according to the example I gave) “any-character”.
  • faq/([^/]+)/([^/]+)/?$ – this rule is almost the same as the previous, except for the URL that would match this rule looks like “faq/any-character/post-slug/”.This will display a post with slug “post-slug” from a FAQ Category with slug “any-character” OR an FAQ archive for a FAQ category with a slug “post-slug” that is child of the “any-character” FAQ category.
  • faq/([^/]+)/([^/]+)/page/(\d{1,})/?$ – this rule will match paginated results for a sub-category archive.
  • faq/([^/]+)/([^/]+)/([^/]+)/?$ – this rule is almost the same as the previous, except for the URL that would match this rule looks like “faq/any-character/sub-category/post-slug/”.This will display a post with slug “post-slug” from a FAQ Category with slug “sub-category”.

The second function on the other hand fixes the issue with the sub-categories. The issue itself consists in the fact, that when you try to load a page with URL faq/category/child-category/, WordPress would try to load a post with slug “child-category” instead of the sub-category with slug “child-category”. The function itself is probably not the best solution to the problem, at least because we have to make an additional query to the database, but it’s the only solution I was able to come-up with Since the function checks if there are posts with slug “child-category”, if you happen to have posts(from the FAQ post type) and categories with the same slug, you might get unexpected results

That’s enough to make the FAQ database run properly, but right now if you want to fully use the new Rewrite rules, you’d have to write every URL manually(I mean, when you link to content from the FAQ database). This is because WordPress doesn’t know how to generate the links, so that they match our idea. That’s why we add a couple of more functions:

The above functions “filter” the output from two built-in WordPress functions, which take care of creating the correct URL’s for all pages, posts, taxonomy archives, etc.

There is one more issue, that could be a serious problem for some users – the duplicate content issue. There is a rather large chance, that Google or another search engine that crawls your website to get quite upset by the fact that it can find identical content in two different URL’s(for instance faq-item/post-2-slug/ and faq/category/sub-category/post-2-slug/). This can be avoided with a function that redirects the users to the correct address, but I’ll leave this up to you

If you want to see the whole thing in action, you can go to Custom Permalinks Test.

9 thoughts on “Custom Permalinks for hierarchical taxonomies

  1. Doesn’t work at all!

    • I’m sorry to hear that it doesn’t work for you Sandrilyon – have you tried updating your permalinks?

      Quite a lot of things can break when working with custom permalinks, so it’s absolutely possible that something didn’t work for you.

  2. Dear Nikola,
    The code is good for me create custom post type permalink like you but I t doesn’t run with pagination.

    Can you tell me the way to fix?

    • Hi,

      Can you try by changing the $new_rules array to this:

      EDIT: I’ve updated the post to reflect the properly working code.


      • Thanks for help me fix the pagination error but the pagination still doesn’t run.
        I go to:
        .com/faq/main-category => it is ok but I click the page 2…it make me go to page 2 of archive page.
        .com/faq/main-category/sub-category => it is ok but I click the page 2, it make me the link .com/faq/main-category/sub-category/page/2 but it still displays result of page 1.

        I tried with your code but It doesn’t run. I hope you can help me. Thanks a lot.

        • Hi again,

          Check-out the article again – I’ve updated it with the proper code(and I’ve switched to using Gists instead of code highlighter – it works better).


  3. Thanks Nikola,
    I am very happy when you updated new code but I tried and there have some errors:
    1- The single custom post type does not recognize the template.
    2- The category / sub-category still has error when click to next page

    I didn’t change or edit your code, I just copy/paste to functions.php for demo and check FAQ custom post type and it didn’t show the right way.
    I think you did a good test for this code and I don’t know how I can’t do this. If you have demo for this version. It’s so good.


    • Did you remember to go to the Settings > Permalinks page?

      In my previous code I was re-building the permalinks on every page load, but since that’s not a good idea I’ve removed that in the revised code.


  4. I tried many times with permalinks but it still doesn’t work for me.
    Do you have any idea? I also tried your code with my null WP version but the same result with error. I will try it more but can you give me some advice?


Leave a Reply

Your email address will not be published. Required fields are marked *