I created a 'forum' taxonomy, using these rules:

    'public' => true,
    'name' => _a('Forums'),
    'singular_name' => _a('Forum'),
    'show_ui' => true,
    'show_in_nav_menus' => true,
    'hierarchical' => true,

    'labels' => array(
      'name' => _a('Forums'),
      'singular_name' => _a('Forum'),
      'search_items' => _a('Search Forums'),
      'popular_items' => _a('Popular Forums'),
      'all_items' => _a('All Forums'),
      'parent_item' => _a('Parent Forum'),
      'parent_item_colon' => _a('Parent Forum:'),
      'edit_item' => _a('Edit Forum'),
      'update_item' => _a('Update Forum'),
      'add_new_item' => _a('Add New Forum'),
      'new_item_name' => _a('New Forum Name'),
    'query_var' => true,
    'rewrite' => array('slug' => 'forums', 'with_front' => false, 'hierarchical' => true),  

In the front-end the URLs looks like:


How can I remove the front slug ("forums")? Ie, change the URLs to:


If I pass a empty slug argument to register_taxonomy() it works, but that causes issues with the permalinks of the post type associated with this taxonomy

  • @One Trick Pony - Have you tried instead of leaving 'slug' => 'forums' blank just removing it altogether and just having 'rewrite' => array('with_front' => false, 'hierarchical' => true)? I think that has worked in the past for me. Also make sure you flush the permalinks. Commented Feb 15, 2011 at 18:32
  • tried that, and the permalinks look the same. Adding 'slug' => '' makes it work, but then posts using this taxonomy generate 404s Commented Feb 15, 2011 at 19:28
  • @One Trick Pony - Besides 'general-discussion' what other top level path segments do you need? Commented Feb 19, 2011 at 5:26
  • any %forum% should be a top level segment Commented Feb 19, 2011 at 12:14
  • @One Trick Pony - I was just hoping your give me some other top level path segments for context. Commented Feb 20, 2011 at 17:49

9 Answers 9



Since writing this WordPress core has added the 'do_parse_request' hook that allows URL routing to be handled elegantly and without the need to extend the WP class. I covered the topic in-depth in my 2014 Atlanta WordCamp talk entitled "Hardcore URL Routing"; the slides are available at the link.


URL Design has been important to be for well over a decade; I even wrote a blog about it several years back. And while WordPress is sum is a brilliant bit of software unfortunately it's URL rewrite system is just short of brain dead (IMHO, of course. :) Anyway, glad to see people caring about URL design!

The answer I'm going to provide is a plugin I'm calling WP_Extended that is a proof of concept for this proposal on Trac (Note that proposal started as one thing and evolved into another, so you have to read the entire thing to see where it was headed.)

Basically the idea is to subclass the WP class, override the parse_request() method, and then assign the global $wp variable with an instance of the subclass. Then within parse_request() you actually inspect the path by path segment instead of using a list of regular expressions that must match the URL in their entirety.

So to state it explicitly, this technique inserts logic in front of the parse_request() which checks for URL-to-RegEx matches and instead first looks for taxonomy term matches, but it ONLY replaces parse_request() and leaves the entire rest of the WordPress URL routing system intact including and especially the use of the $query_vars variable.

For your use-case this implementation only compares URL path segments with taxonomy terms since that's all you need. This implementation inspects taxonomy terms respecting parent-child term relationships and when it finds a match it assigns the URL path (minus leading and trailing slashes) to $wp->query_vars['category_name'], $wp->query_vars['tag'] or $wp->query_vars['taxonomy'] & $wp->query_vars['term'] and it bypasses the parse_request() method of the WP class.

On the other hand if the URL path does not match a term from a taxonomy you've specified it delegates URL routing logic to the WordPress rewrite system by calling the parse_request() method of the WP class.

To use WP_Extended for your use-case you'll need to call the register_url_route() function from within your theme's functions.php file like so:

function init_forum_url_route() {

What here is the source code for the plugin:

Filename: wp-extended.php
Plugin Name: WP Extended for Taxonomy URL Routes
Author: Mike Schinkel
function register_url_route($args=array()) {
  if (isset($args['taxonomy']))
class WP_Extended extends WP {
  static $taxonomies = array();
  static function on_load() {
  static function register_taxonomy_url($taxonomy) {
    self::$taxonomies[$taxonomy] = get_taxonomy($taxonomy);
  static function setup_theme() { // Setup theme is 1st code run after WP is created.
    global $wp;
    $wp = new WP_Extended();  // Replace the global $wp
  function parse_request($extra_query_vars = '') {
    $path = $_SERVER['REQUEST_URI'];
    $domain = str_replace('.','\.',$_SERVER['SERVER_NAME']);
    //$root_path = preg_replace("#^https?://{$domain}(/.*)$#",'$1',WP_SITEURL);
$root_path = $_SERVER['HTTP_HOST'];

    if (substr($path,0,strlen($root_path))==$root_path)
      $path = substr($path,strlen($root_path));
    list($path) = explode('?',$path);
    $path_segments = explode('/',trim($path,'/'));
    $taxonomy_term = array();
    $parent_id = 0;
    foreach(self::$taxonomies as $taxonomy_slug => $taxonomy) {
      $terms = get_terms($taxonomy_slug);
      foreach($path_segments as $segment_index => $path_segment) {
        foreach($terms as $term_index => $term) {
          if ($term->slug==$path_segments[$segment_index]) {
            if ($term->parent!=$parent_id) { // Make sure we test parents
              $taxonomy_term = array();
            } else {
              $parent_id = $term->term_id; // Capture parent ID for verification
              $taxonomy_term[] = $term->slug; // Collect slug as path segment
              unset($terms[$term_index]); // No need to scan it again
      if (count($taxonomy_term))
    if (count($taxonomy_term)) {
      $path = implode('/',$taxonomy_term);
      switch ($taxonomy_slug) {
        case 'category':
          $this->query_vars['category_name'] = $path;
        case 'post_tag':
          $this->query_vars['tag'] = $path;
          $this->query_vars['taxonomy'] = $taxonomy_slug;
          $this->query_vars['term'] = $path;
    } else {
      parent::parse_request($extra_query_vars); // Delegate to WP class


Although for a given site I think this technique works brilliantly but this technique should NEVER be used for a plugin to be distributed on WordPress.org for others to use. If it is at the core of a software package based on WordPress then that might be okay. Otherwise this technique should be limited to improving the URL routing for a specific site.

Why? Because only one plugin can use this technique. If two plugins try to use it they will conflict with each other.

As an aside this strategy can be expanded to generically handle practically every use-case pattern that could be required and that's what I intend to implement as soon as I either find the spare time or a client who can sponsor the time that it would take to build fully generic implementations.


I wrote this to override parse_request() which is a very large function, and it is quite possible that I missed a property or two of the global $wp object that I should have set.. So if something acts wonky let me know and I'll be happy to research it and revise the answer if need be.


  • After writing this I realized that I tested for categories instead of for taxonomy terms in general so the above will not work for the 'forum' taxonomy however I will revise it to work later today... Commented Feb 23, 2011 at 12:58
  • So I've updated the code to address the problem I mention in the prior comment. Commented Feb 23, 2011 at 23:55
  • can't get this work... do I need to change the rewrite rules? Commented Feb 24, 2011 at 10:51
  • @One Trick Pony - A little more diagnostic information would help. :) What did you try? What happens when you enter the URLs into your browser? Did you per chance call your taxonomy 'forums' rather than 'forum'? Are you expecting the URLs that link to these pages to change (if yes, no wonder, my code does not address the printing of URLs, only routing of URLs.) Commented Feb 24, 2011 at 19:38
  • no, I can change the URLs (I think its the term_link function that I need to hook into for that). site/rootforum/ works, but site/rootforum/subforum/ doesn't (404 error) ... Commented Feb 24, 2011 at 19:56

Simple, really.

Step 1: Stop using the rewrite parameter at all. We're going to roll your own rewrites.


Step 2: Set verbose page rules. This forces normal Pages to have their own rules instead of being a catch-all at the bottom of the page.

Step 3: Create some rewrite rules to handle your use cases.

Step 4: Manually force a flush rules to happen. Easiest way: go to settings->permalink and click the save button. I prefer this over a plugin activation method for my own usage, since I can force the rules to flush whenever I change things around.

So, code time:

function test_init() {
    // create a new taxonomy
            'query_var' => true,
            'rewrite' => false,

    // force verbose rules.. this makes every Page have its own rule instead of being a 
    // catch-all, which we're going to use for the forum taxo instead
    global $wp_rewrite;
    $wp_rewrite->use_verbose_page_rules = true;

    // two rules to handle feeds

    // one rule to handle paging of posts in the taxo

    // one rule to show the forum taxo normally
    add_rewrite_rule('(.+)/?$', 'index.php?forum=$matches[1]');

add_action( 'init', 'test_init' );

Remember that after adding this code, you need to have it active when you go flush the permalink rules (by Saving the page on Settings->Permalinks)!

After you've flushed the rules and saved to the database, then /whatever should go to your forum=whatever taxonomy page.

Rewrite rules really aren't that difficult if you understand regular expressions. I use this code to help me when debugging them:

function test_foot() {
    global $wp_rewrite;
    echo '<pre>';
    echo '</pre>';

This way, I can see the current rules at a glance on my page. Just remember that given any URL, the system starts at the top of the rules and goes down through them until it finds one that matches. The match is then used to rewrite the query into a more normal looking ?key=value set. Those keys get parsed into what goes into the WP_Query object. Simple.

Edit: Side note, this method will probably only work if your normal custom post structure starts with something that isn't a catchall, like %category% or some such thing like that. You need to start it with a static string or a numeric, like %year%. This is to prevent it catching your URL before it gets to your rules.

  • If you want easier debugging of your rewrite rules, I (again) recommend my rewrite analyzer plugin, which allows you to try out the rules and see the query variables on the fly.
    – Jan Fabry
    Commented Feb 24, 2011 at 12:28
  • Unfortunately the current URL rewrite system forces the flattening of all potential URL patterns into a large list vs. following the inherent tree structure of URL paths. The current setup can't conveniently match an array of literals such as categories, or forum names; as you know it forces all "Page" URLs to be evaluated first. Matching by path segment and matching in multiple ways (array of literals, categories, tags, tax-terms, user names, post types, post names, callbacks, filter hooks, and finally RegEx) would scale for complexity better, and would be easier to understand. Commented Feb 25, 2011 at 4:42
  • Mike: Actually, that is not easier to understand at all, because I haven't got the first clue WTF you're talking about there. Your ideas of URL routing are confusing and difficult, and as you probably know, I disagree with them. The flat search makes more sense and is more flexible than you tend to give it credit for. Most people don't want all that needless complexity in their URLs, and almost nobody needs it either.
    – Otto
    Commented Feb 25, 2011 at 17:03
  • Thanks, but I think I already tried this before (wordpress.stackexchange.com/questions/9455/…) Commented Feb 25, 2011 at 19:00
  • Fortunately WordPress Answers now allows people who do want control of their URLs to finally have a voice, and they seem to be many (100+). But I respect that you may not be able to follow my example prior to a full implementation. I predict that, once the approach I am advocating is fully implemented in a plugin and after about 6-12 months it will have become the preferred way for WordPress-based CMS sites to route their URLs. So lets resume this debate in about 9 months. Commented Mar 1, 2011 at 17:38

You will not be able to do this using WP_Rewrite alone, since it can't distinguish between term slugs and post slugs.

You have to also hook into 'request' and prevent the 404, by setting the post query var instead of the taxonomy one.

Something like this:

function fix_post_request( $request ) {
    $tax_qv = 'forum';
    $cpt_name = 'post';

    if ( !empty( $request[ $tax_qv ] ) ) {
        $slug = basename( $request[ $tax_qv ] );

        // if this would generate a 404
        if ( !get_term_by( 'slug', $slug, $tax_qv ) ) {
            // set the correct query vars
            $request[ 'name' ] = $slug;
            $request[ 'post_type' ] = $cpt_name;
            unset( $request[$tax_qv] );

    return $request;
add_filter( 'request', 'fix_post_request' );

Note that the taxonomy has to be defined before the post type.

This would be a good time to point out that having a taxonomy and a post type with the same query var is a Bad Idea.

Also, you won't be able to reach posts that have the same slug as one of the terms.

  • Agreed that having a taxonomy and a post type with the same query var is a Bad Idea, but that might imply to people having a taxonomy and a post type with the same name is a bad idea, which is not the case. If using the same name then only one of the two should have a query var. Commented Feb 20, 2011 at 17:51

I'd take a look at the code of the top level cats plugin:


You could easily adapt that so it's looking for your custom taxonomy slug by changing the

$category_base = get_option('category_base');

on line 74 to something like like:

$category_base = 'forums';
  • Might work for categories, but it doesn't for custom taxonomies (at least in wp 3.1)... I managed to change the URLs, but I get 404 errors Commented Feb 15, 2011 at 19:29

I'd suggest taking a look at the Custom Post Permalinks plugin. I don't have time to test right now, but it may help with your situation.

  • 1
    it doesn't, it only handles posts, not taxonomies, and even if it would, I'd have to add some kind of prefix before %forum%, which is exactly what I'm trying to avoid... Commented Feb 17, 2011 at 22:14

Since I'm a familiar with your other question, I'll answer with that in mind.

I haven't tested this at all, but it might work if you execute this once right after you register all the permastructs you want.:

class RRSwitcher {
  var $rules;
  function RRSwitcher(){
    add_filter( 'topic_rewrite_rules', array( $this, 'topics' ) );
    add_filter( 'rewrite_rules_array', array( $this, 'rules' ) );
  function topics( $array ){
    $this->rules = $array;
    return array();
  function rules( $array ){
    return array_merge( (array)$array, (array)$this->rules );
$RRSwitcher = new RRSwitcher();
global $wp_rewrite;
$wp_rewrite->use_verbose_rules = true;

What this does: it removes the rewrite rules generated from the topics permalink from the normal flow of the rules array and re-merges them at the end of the array. This prevents those rules from interfering with any other rewrite rules. Next, it forces verbose rewrite rules (each page gets an individual rule with a specific regular expression). This prevents the pages from interfering with your topic's rules. Finally, it executes a hard flush (make sure your .htaccess file is writable, otherwise this won't work) and saves the very large very complicated array of rewrite rules.

  • tried it, nothing changes Commented Feb 17, 2011 at 23:21

There is a plugin for this.

It removes the type slug by adding a specific rule for each custom post type page.

  • The link does not work anymore.
    – Philipp
    Commented Jul 2, 2020 at 9:16

Not sure if this will work for taxonomies, but it worked for custom post types

Although it hasn't been updated for 2 years, the below plugin worked for me: http://wordpress.org/plugins/remove-slug-from-custom-post-type/

FYI I'm running WP 3.9.1 with WP Types 1.5.7


Use a slash as value for slug... 100% working

'rewrite' => array(
    'slug'       => '/', 
    'with_front' => FALSE
  • 6
    not quite, this causes all of the page post type to 404.
    – Milo
    Commented Apr 9, 2015 at 15:20

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.