Advanced Taxonomy Queries with Pretty URLs

WordPress 3.1 introduced the best new feature I failed to notice – built-in support for filtering posts by multiple taxonomies.

For a post index or custom post type archive, instead of being constrained to queries for one taxonomy like this:

www.example.com/post-type/taxonomy_x/term_x/

We can do this:

www.example.com/post-type/?taxonomy_y=term_y&taxonomy_x=term_x

WordPress will automatically filter the posts to include only those posts tagged with term_y in taxonomy_y and term_x in taxonomy_x.

What is it Good For?

Event's Custom Post Type MenuImagine you have an Event post type with both a Location and Industry taxonomy. You want to give site visitors a way to filter events to show only events in certain locations & relevant to certain industries. For example, a URL to show only events in Sydney for the Web Development industry.

This is exactly the problem I was given while working with_FindingSimple on the Australian Government’s new EEX site.

Previously, we would have to do all sorts of manual SQL madness to get this type of thing to work, but now WordPress can do it all for us… if we ask nicely.

Taxonomy Queries

The Codex has a good article that introduces taxonomy queries. Otto has also published a great tutorial to cover the fundamentals of running advanced taxonomy queries.

Both of these articles use the query_posts function to create a taxonomy query. They stop short of showing how it’s done with URLs.

It’s crazy simple with URLs, as with the example I opened with, just add the taxonomy as a parameter and the term/s as the value of that parameter.

Returning to the events example, this URL will filter events to show only those in New South Wales and of relevance to the web industry.

www.example.com/events/?location=nsw&industry=web

Voilà, WordPress will filter the events automatically.

Now you may think – my visitors can manually enter unknown search parameters to filter posts, whoop-dee-doo! - we’ll get to a solution for this soon. For now, let’s take it a little further with the taxonomy filters.

Let’s say we want to show events in New South Wales OR Queensland, which are related to the web AND mobile industries. We can use the following URL:

www.example.com/events/?location=nsw,qld&industry=web+mobile

Simple as that. WordPress will now filter events for two locations using an OR operator and for two industries using an AND operator.

All we had to do was use the plus (+) symbol to tell WordPress to select events tagged with both industries and a comma (,) to tell WordPress to select events in either location.

Pretty URL Taxonomy Queries

Now let’s take it up a notch.

One of WordPress’s best features is its pretty permalinks. Instead of URLs like:

http://example.com?page=123

WordPress can use nice URLs like:

http://example.com/page-name/

Search engines love it. Humans love it.

So how can we create a pretty permalink which WordPress will translate into an advanced taxonomy query like those above?

Something like:

www.example.com/events/location/nsw,qld/industry/web+mobile/

It’s a 2 step process:

  1. Tell WordPress about the URL structure, and how it should handle requests with that URL structure, that is, give WordPress the rewrite rules;
  2. Generate the pretty URLs.

Step 1: The Rewrite Rules

We need to add rewrite rules to tell WordPress what to do with our pretty URLs.

A rewrite rule is a key => value pair where the key is the pretty URL to match against and the value is the URL WordPress can process. To set a rule, use an associative array and add it to the global $wp_rewrites object.

The best hook for adding custom rewrite rules is the 'generate_rewrite_rules' hook. For example:

function eg_add_rewrite_rules() {
	global $wp_rewrite;

	$new_rules = array(
		'event/industry/(.+)/?$' => 'index.php?post_type=eg_event&industry=' . $wp_rewrite->preg_index(1)
	);
	$wp_rewrite->rules = $new_rules + $wp_rewrite->rules;
}
add_action( 'generate_rewrite_rules', 'eg_add_rewrite_rules' );

A few things to pay special attention to in this example:

  • The new rules get prepended to the existing rewrite rules – this is very important, if the code was $wp_rewrite->rules + $new_rules instead of $new_rules + $wp_rewrite->rules WordPress’s default rewrites would capture the URL before our rules even got a look-in.
  • The function uses the $wp_rewrite back reference as represented by $wp_rewrite->preg_index(1) instead of using a vanilla regex back reference, like $1
  • This function can only be run after the $wp_rewrite has been set up in order for $wp_rewrite->preg_index(1) to exist.

Once you have added the rules and hooked to generate_rewrite_rules, you must visit the Permalink admin page to have WordPress call the generate_rewrite_rules hook. In development, if you are regularly changing the rules, you can also use the flush_rewrite_rules() function. This is a very resource intensive function though, so best not to use it in live code unless it’s only called once on plugin or theme activation.

Step 1.1: Rewrite Rules Powerset

Now for the fun part.

The example above hardcodes the industry taxonomy. This is OK for one taxonomy, but we’ve got at least two (industry & location), and we may add more in the future. What we need then is some rewrite rules to match every possible combination and permutation of taxonomies for the event post type.

You may recognise this as a set theory problem. Each taxonomy with its terms represents a set, we want the power set of all taxonomies. There are a few PHP classes around the place to compute power sets. I tried one of these, but it created 24 rules for the 4 taxonomies I was using. Worse still, this verbosity would expand dramatically with each new taxonomy. The number of rules is the factorial of the number of taxonomies – 4! = 24, 5! = 120. I wanted a more concise method.

After chatting with dd32 about the regex available in rewrites rules, I realised there was a much more concise method which would add just one line per taxonomy. The regex for the rewrites can match anything, so why not match the taxonomy and the taxonomy term.

Returning to the location & industry example, we get two rules that look like this:

function eg_add_rewrite_rules() {
	global $wp_rewrite;

	$new_rules = array(
		'event/(industry|location)/(.+?)/(industry|location)/(.+?)/?$' => 'index.php?post_type=eg_event&' . $wp_rewrite->preg_index(1) . '=' . $wp_rewrite->preg_index(2) . '&' . $wp_rewrite->preg_index(3) . '=' . $wp_rewrite->preg_index(4),
		'event/(industry|location)/(.+)/?$' => 'index.php?post_type=eg_event&' . $wp_rewrite->preg_index(1) . '=' . $wp_rewrite->preg_index(2)
	);
	$wp_rewrite->rules = $new_rules + $wp_rewrite->rules;
}
add_action( 'generate_rewrite_rules', 'eg_add_rewrite_rules' );

See what’s happening there? We’re matching either industry or location as a taxonomy.

Let’s take a look at how these rewrite rules would apply to the following URL.

www.example.com/events/location/nsw/industry/web/

In the first instance, our regex would capture the following:

  • $wp_rewrite->preg_index(1) would capture the taxonomy location
  • $wp_rewrite->preg_index(2) would capture the location taxonomy term nsw
  • $wp_rewrite->preg_index(3) would capture the taxonomy industry
  • $wp_rewrite->preg_index(4) would capture the industry taxonomy term web

The URL it would pass to WordPress for processing would then be:

www.example.com/events/?location=nsw&industry=web

Which is the URL we saw in the opening example.

Step 2: Creating the Permalink

Earlier I mentioned how useless it is having a URL structure that requires the user to know parameters and their possible values. Let’s now create a function to generate permalinks with appropriate filters parameters. We could use this function in a widget or hardcoded sidebar on our post type archive page.

The function needs to:

  1. Maintain taxonomy term filters for the current page request;
  2. Add taxonomy terms to a permalink, but only when they are not already set in the URL;
  3. Remove terms when they are already set in the URL; and
  4. Combine terms if a given term is within a taxonomy that is already being used to filter.

Taking these criteria into account, we get something like the eg_get_filter_permalink function below.

This function takes a $taxonomy_slug slug and a taxonomy $term to filter by. It combines terms of the same taxonomy with a plus (+), so WordPress will use an AND operator to combine the terms. You could just as easily set this to a comma (,) for WordPress to combine terms with an OR operator.

I’ll let the code do the rest of the talking.

function eg_get_filter_permalink( $taxonomy_slug, $term ) {
	global $wp_query;

	// If there is already a filter running for this taxonomy
	if( isset( $wp_query->query_vars[$taxonomy_slug] ) ){
		// And the term for this URL is not already being used to filter the taxonomy
		if( strpos( $wp_query->query_vars[$taxonomy_slug], $term ) === false ) {
			// Append the term
			$filter_query = $taxonomy_slug . '/' . $wp_query->query_vars[$taxonomy_slug] . '+' . $term;
		} else {
			// Otherwise, remove the term
			if( $wp_query->query_vars[$taxonomy_slug] == $term ) {
				$filter_query = '';
			} else {
				$filter = str_replace( $term, '', $wp_query->query_vars[$taxonomy_slug] );
				// Remove any residual + symbols left behind
				$filter = str_replace( '++', '+', $filter );
				$filter = preg_replace( '/(^\+|\+$)/', '', $filter );
				$filter_query = $taxonomy_slug . '/' . $filter;
			}
		}
	} else {
		$filter_query = $taxonomy_slug . '/' . $term;
	}

	// Maintain the filters for other taxonomies
	if( isset( $wp_query->tax_query ) ) {

		foreach( $wp_query->tax_query->queries as $query ) {

			$tax = get_taxonomy( $query['taxonomy'] );

			// Have we already handled this taxonomy?
			if( $tax->query_var == $taxonomy_slug )
				continue;

			// Make sure taxonomy hasn't already been added to query string
			if( strpos( $existing_query, $tax->query_var ) === false )
				$existing_query .= $tax->query_var . '/' . $wp_query->query_vars[$tax->query_var] . '/';
		}

	}

	if( isset( $existing_query ) )
		$filter_query = $existing_query . $filter_query;

	return trailingslashit( get_post_type_archive_link( 'eg_event' ) . $filter_query );
}

Now to create a pretty permalink for our filter, we can use this function something like this:

// Link to page with only events in NSW
echo eg_get_filter_permalink( 'location', 'nsw' );

Even better, we can loop over all taxonomies and their terms to output every possible filter link.

Bonus Prize: A Rewrite Rule Generator

As you’ve read this far, I think you deserve a reward.

It’s quite likely you will at some stage want to programmatically generate rewrite rules for multiple custom post types with various taxonomies. You could do it manually, but that’s no fun. Let’s use an automatic rewrite rule generator.

The function below takes a post type as the first parameter and creates a series of rewrite rules for each of its taxonomies. The function also includes an optional second parameter, $query_vars, which is an array of any non-taxonomy query variables you may want to add rewrite rules for. It’s ideal for adding query vars for advanced metadata queries. For example, our event post type could use a date query variable.

/**
 * Generates all the rewrite rules for a given post type.
 *
 * The rewrite rules allow a post type to be filtered by all possible combinations & permutations
 * of taxonomies that apply to the specified post type and additional query_vars specified with
 * the $query_vars parameter.
 *
 * Must be called from a function hooked to the 'generate_rewrite_rules' action so that the global
 * $wp_rewrite->preg_index function returns the correct value.
 *
 * @param string|object $post_type The post type for which you wish to create the rewrite rules
 * @param array $query_vars optional Non-taxonomy query vars you wish to create rewrite rules for. Rules will be created to capture any single string for the query_var, that is, a rule of the form '/query_var/(.+)/'
 *
 * @author Brent Shepherd <me@brentshepherd.com>
 * @since 1.0
 */
function eg_generate_rewrite_rules( $post_type, $query_vars = array() ) {
	global $wp_rewrite;

	if( ! is_object( $post_type ) )
		$post_type = get_post_type_object( $post_type );

	$new_rewrite_rules = array();

	$taxonomies = get_object_taxonomies( $post_type->name, 'objects' );

	// Add taxonomy filters to the query vars array
	foreach( $taxonomies as $taxonomy )
		$query_vars[] = $taxonomy->query_var;

	// Loop over all the possible combinations of the query vars
	for( $i = 1; $i <= count( $query_vars );  $i++ ) {

		$new_rewrite_rule =  $post_type->rewrite['slug'] . '/';
		$new_query_string = 'index.php?post_type=' . $post_type->name;

		// Prepend the rewrites & queries
		for( $n = 1; $n <= $i; $n++ ) {
			$new_rewrite_rule .= '(' . implode( '|', $query_vars ) . ')/(.+?)/';
			$new_query_string .= '&' . $wp_rewrite->preg_index( $n * 2 - 1 ) . '=' . $wp_rewrite->preg_index( $n * 2 );
		}

		// Allow paging of filtered post type - WordPress expects 'page' in the URL but uses 'paged' in the query string so paging doesn't fit into our regex
		$new_paged_rewrite_rule = $new_rewrite_rule . 'page/([0-9]{1,})/';
		$new_paged_query_string = $new_query_string . '&paged=' . $wp_rewrite->preg_index( $i * 2 + 1 );

		// Make the trailing backslash optional
		$new_paged_rewrite_rule = $new_paged_rewrite_rule . '?$';
		$new_rewrite_rule = $new_rewrite_rule . '?$';

		// Add the new rewrites
		$new_rewrite_rules = array( $new_paged_rewrite_rule => $new_paged_query_string,
			 						$new_rewrite_rule       => $new_query_string )
							 + $new_rewrite_rules;
	}

	return $new_rewrite_rules;
}

And there we have it. A full arsenal to generate and accept pretty URLs for advanced taxonomy queries. Not as simple as appending ?taxonomy=term parameters to a URL, but more user and search engine friendly.

Update:

To help you debug your rewrite rules, download and install Monkeyman’s Rewrite Analyzer. It’s simply excellent.

About Brent

Born to code.
This entry was posted in Blogdex, WordPress and tagged , , , , . Bookmark the permalink.

119 Responses to Advanced Taxonomy Queries with Pretty URLs

  1. Great post Brent!

    I had used the multi taxonomy queries in WordPress, but had never taken the time to implement custom rewrite rules for them.

    James

    • Brent says:

      Hey James, as the post demonstrates, there is a big gap between the time it takes to implement multi-tax queries with GET parameters compared to pretty URLs, so 9 times out of 10 the GET parameter approach would be the only feasible option. For the project I was working on, pretty URLs were the best option though so it was worth the extra time.

      I hope this post helps others get there quicker now though!

  2. Joey says:

    Awesome article. I’ve been looking and looking for a good example of multiple query vars and rewrite rules. I wish your post existed a month ago!

    Oh, I think you have a syntax error in eg_generate_rewrite_rules.

  3. Thank you for this awesome tip !
    Awesome to improve wordpress performances.

    I wrote a small plugin to make it dynamic for your taxonomy, if you want more informations you have my e-mail I guess

  4. wan says:

    Hi Brent,

    I been trying to filter specific custom post type by taxonomies, where the taxonomy is also shared by different custom post types. I used some plugin to sort the taxonomy, but it is directing to taxonomy archive instead of sorting within the specific custom post type archive.
    I been trying to do /url/hotelreviews/location=singapore where hotelreviews is post type and location is taxonomy. but it keep directing to /url/location=singapore. Since it is taxonomy archive, it get all custom post type of the same tax=location=singapore all listed (like hotel reivews and restaurant reviews type all listed). but i want only filter by location=singapore in specific post type hotel reviews.

    Any advice that your tutorial on rewrite can help?

    • Brent says:

      Hi Wan, make sure you specify the post type in your rewrite rules.

      Your code will need to look something like this:

      function add_hotelreviews_rewrite_rules() {
          global $wp_rewrite;
       
          $new_rules = array(
              'hotelreviews/location/(.+)/?$' => 'index.php?post_type=hotelreviews&location=' . $wp_rewrite->preg_index(2)
          );
          $wp_rewrite->rules = $new_rules + $wp_rewrite->rules;
      }
      add_action( 'generate_rewrite_rules', 'add_hotelreviews_rewrite_rules' );
      
  5. Pingback: How WordPress Taxonomy Queries Could Be More Awesomer | MaisonBisson.com

  6. Hi Brent,

    Your code about eg_generate_rewrite_rules still contains an error. I believe some parts are missing. It would be great if you could fix it up right again ;)

    Many thanks!

  7. Hey Brent … first, thanks for presenting this code… we’ve been able to use the ideas on a few sites recently.

    We’ve encountered some issues though (most recently on a sub-blog on a multisite install) – in case anyone else encounters similar:

    1. we found if the template taxonomy.php was present, WordPress was using that rather than our archive-cpt.php to display the posts. It was getting passed the correct posts to show based on the url (cpt, taxonomy 1, taxonomy 2 parameters), but was choosing taxonomy.php as the template to use. Remove the taxonomy.php template and everything works fine.

    2. we had some problems hooking on generate_rewrite_rules (instead we had to hook on init to get them picked up on – in some cases)

    3. we found modifying the rewrite array directly caused problems (WSOD) – but, instead using the add_rewrite_rule() function call worked without problem. I suspect this is probably the safest option anyway. Note: we only had this problem on our live environment; on our dev environment, both worked.

    Cheers, Roger

  8. jam says:

    Hi. Excelent tutorial. But I have a question regarding this url
    http://www.example.com/events/location/nsw,qld/industry/web+mobile/ What would be themplate wordpress would try to load?

  9. jam says:

    What will happen, if I have hierarchical taxonomy like categories? for example
    Add a Country/City style and add a City term to post. How would wordpress manage that then?

  10. ByteBros says:

    Great Post!
    But I found one mistake:
    at $new_rules = array(rule1, rule2) there is missing a comma between the 2 rules.
    And perhaps you could mention, that in the Dashboard the Permalink-Structure has to be saved once (without changing anything) to get the code working?

  11. Attila says:

    Hi, excelent tutorial.
    What do you think, wolud it be possible to extend your permalink function with a kind of
    remove_query_arg(), add_query_arg() functionality?

    Cheers, Attila

    • Brent says:

      Hi Attila, I can definitely see how that would work. I did have individual functions for that, but not a central super function. If there are hooks in add_query_arg() you wouldn’t even need a whole new function.

  12. i had a hell of a time actually getting the rewrite generator launched, so for anyone that is stuck with that, here is the function to add:

    
    function mv_add_rewrite_rule($wp_rewrite){
    	$new_rules = eg_generate_rewrite_rules('product');
    	$wp_rewrite->rules = $new_rules + $wp_rewrite->rules;
    }
    add_action('generate_rewrite_rules','mv_add_rewrite_rule',99);
    

    i love this function, but i’m looking in to how to make it work with the rewrite slugs of the taxonomies versus their query vars. i’m a little confused by preg_index… if we redefine the $query_vars variable to hold the rewrite slugs, then i’m not sure where to “translate” those slugs into their proper query vars

    		for( $n = 1; $n preg_index( $n * 2 - 1 ) . '=' . $wp_rewrite->preg_index( $n * 2 );
    		}
    

    for instance, with your code my URL works with

    product_cat/fabrics/pa_color/red

    but, eventually, i need to it be:

    product-category/fabrics/color/red

    • Brent says:

      Hi Kathy, if you want the query var to be product-category instead of product_cat, you don’t need to mess with the rewrites but rather go straight to the source and set the query_var value in the register_taxonomy() function call (function reference in the Codex).

      For example:

      register_taxonomy( 'product_cat', 'product', array( 'query_var' => 'product-category' ) );
      
    • pabbles says:

      I’m new to the whole add_action / hooks thing, and your comment helped me figure out how and where to use Brent’s powerful function. Thanks :D

  13. Rey says:

    Reblogged this on Reinaldo Ferroe comentado:
    Artigo mais que interessante :-)
    Tks i.am

  14. eadadmin says:

    This is great, and worked perfectly for my custom taxonomies and post types, thank you! Any thoughts about adding it to a post type of post and the delivered category and tag taxonomies? So a permalink might be site.com/category/cat1/tag/tag1 or another might be site.com/category/cat1/customtax/tax1. Thanks again!

  15. eadadmin says:

    Also, sorry to comment again – used Kathy’s function to add the rewrite rules and it’s working swimmingly for one post type – how would i add it to multiple post types? sorry if that’s a newbie php question! thank you!

    • Brent says:

      You need to call eg_generate_rewrite_rules( $product_type ) for each of the post types.

      Something like the following should work (but is untested):

      function eg_add_rewrite_rule(){
      	global $wp_rewrite;
      
      	$new_rules = array();
      	$post_types = array( 'product', 'order', 'event' );
      
      	foreach( $post_types as $post_type )
      		$new_rules = eg_generate_rewrite_rules( $post_type ) + $new_rules;
      
      	$wp_rewrite->rules = $new_rules + $wp_rewrite->rules;
      }
      add_action( 'generate_rewrite_rules', 'eg_add_rewrite_rule' );
      

      Where $post_types is an array of all the post types you want to create rewrite rules for.

      • eadadmin says:

        thanks so much! worked like a charm. and i see in the code why this doesn’t work for the default post type of post (it wants that permalink). i’ll have to play to see if i can get it to work. thank you for your help!

  16. weal says:

    Even better, we can loop over all taxonomies and their terms to output every possible filter link. :) How is that :)

  17. gotmaids says:

    This code do it

    Classifications
    $taxonomy,
    'orderby' => $orderby,
    'pad_counts' => $pad_counts,
    'hierarchical' => $hierarchical,
    'title_li' => $title,
    'hide_empty' => $empty,
    'echo' => $echo
    );
    ?>

    category_nicename.'' ) ."";
    $option = 'cat_name.'">';
    $option .= $classification->cat_name;
    $option .= ' ('.$classification->category_count.')';
    $option .= '
    ';
    echo $option;
    }

    ?>

  18. Pankaj Agrawal says:

    This is very nice article
    helps me a lot
    thanks

  19. Phil says:

    Hi,

    i have a question at step 1. Where do i have to register this function. I mean in wich file?

    Best regards

  20. Pingback: If you want a way to generate URL’s… « Random Thoughts

  21. Thanks you soo much
    i was doing this in an ugly way
    now i am really happy with my client :)

  22. Do you know where I can find information on just passing variables and not a taxonomy? Essentially I’m querying a database with a whole bunch of variables. Which are not posts. Making them posts would add about 20000 posts to my database. I already have all my queries written but I just want a pretty url.

    http://www.mysite.com/MyPage/?apples=1&pears=2&peaches=3
    into
    http://www.mysite.com/MyPage/apples/1/pears/2/peaches/3

    I’ve been searching for my answer for over 1 year and never found it.

    Thanks!

    • Brent says:

      Danielle, what you need are custom rewrite rules for your variables.

      Something like:

      function eg_add_rewrite_rules() {
          global $wp_rewrite;
       
          $new_rules = array(
              'MyPage/apples/(.+)/pears/(.+)/?$' => 'index.php?pagename=mypage&apples=' . $wp_rewrite->preg_index(1) . '&pears=' . $wp_rewrite->preg_index(2)
          );
          $wp_rewrite->rules = $new_rules + $wp_rewrite->rules;
      }
      add_action( 'generate_rewrite_rules', 'eg_add_rewrite_rules' );
      

      And add apples and pears as custom query variables:

      function eg_add_query_vars( $query_vars ) {
          $new_vars = array( 'apples', 'pears' );
      
          return array_merge( $new_vars, $query_vars );
      }
      add_filter( 'query_vars', 'eg_add_query_vars' );
      

      And then a way to create the actual links for your custom variables used to hyperlink content.

      It’s really a lot more complicated than that, and you’ll need to learn a lot so I’d recommend you head to Google to find tutorials on “Custom Rewrite Rules”, like this one for Tutsplus

      • templaries says:

        I have got the same problem:

        For example, i need: http://www.example.org/location/canada/category/dogs

        Then i have wrote this code:

        function eg_add_query_vars( $query_vars ) {
        $new_vars = array( ‘location’, ‘category’ );

        return array_merge( $new_vars, $query_vars );
        }
        add_filter( ‘query_vars’, ‘eg_add_query_vars’ );

        function eg_add_rewrite_rules() {
        global $wp_rewrite;

        $new_rules = array(
        ‘en/(.+)/de/(.+)/?$’ => ‘index.php?location=’ . $wp_rewrite->preg_index(1) . ‘&category_name=’ . $wp_rewrite->preg_index(2)
        );
        $wp_rewrite->rules = $new_rules + $wp_rewrite->rules;
        }
        add_action( ‘generate_rewrite_rules’, ‘eg_add_rewrite_rules’ );

        It does not work.

        • templaries says:

          Sorry this is the correct code:

          function eg_add_query_vars( $query_vars ) {
              $new_vars = array( 'location', 'category' );
          
              return array_merge( $new_vars, $query_vars );
          }
          add_filter( 'query_vars', 'eg_add_query_vars' );
          
          function eg_add_rewrite_rules() {
              global $wp_rewrite;
           
              $new_rules = array(
                  'location/(.+)/category/(.+)/?$' => 'index.php?location=' . $wp_rewrite->preg_index(1) . '&category_name=' . $wp_rewrite->preg_index(2)
              );
              $wp_rewrite->rules = $new_rules + $wp_rewrite->rules;
          }
          add_action( 'generate_rewrite_rules', 'eg_add_rewrite_rules' );
          
          • Brent says:

            The regex match I provided might be too greedy on second look, try:

            function eg_add_rewrite_rules() {
                global $wp_rewrite;
             
                $new_rules = array(
                    'location/(.+?)/category/(.+?)/?$' => 'index.php?location=' . $wp_rewrite->preg_index(1) . '&category_name=' . $wp_rewrite->preg_index(2)
                );
                $wp_rewrite->rules = $new_rules + $wp_rewrite->rules;
            }
            add_action( 'generate_rewrite_rules', 'eg_add_rewrite_rules' );
            

            To help you debug, checkout Monkeyman Rewrite Analyzer

      • danielleroux says:

        Thanks a lot. It worked! finally after so much time!

  23. topmaxtech says:

    thank you very match
    that’s good info,

  24. Matt says:

    I am having problems getting the basic URL query to work as when I put the equivalent of http://www.example.com/post-type/?taxonomy_y=term_y&taxonomy_x=term_x in, my installation of WordPress returns the same page it would had I not put in the variables, so the equivalent of http://www.example.com/post-type/

    Any suggestions of what might be causing this? (I have deactivated all plugins and still no luck)

    Thanks,Matt

    • Brent says:

      Hrm, interesting. Which version of WordPress are you using?

      Also, can you share the code used to register the post type & taxonomies? (between <pre></pre> tags in the comment or just link to a Gist).

  25. Mark Allenr says:

    Great post, thanks for this. It doesn’t take nicely to taxonomies where the ‘rewrite’ argument is supplied. I haven’t had time to look at that, but otherwise this works great.

  26. Shafraz says:

    Hi, can you please tell me what is wrong with my code below.

    function eg_add_query_vars( $query_vars ) {
        $new_vars = array( 'network-camera', 'camera-brand', 'camera-series' );
    
        return array_merge( $new_vars, $query_vars );
    }
    add_filter( 'query_vars', 'eg_add_query_vars' );
    
    function eg_add_rewrite_rules() {
        global $wp_rewrite;
     
        $new_rules = array(
            'network-camera/(.+)/camera-series/(.+)/camera-brand/(.+)/?$' => 'index.php?network-camera=' . $wp_rewrite->preg_index(1) . '&camera-series=' . $wp_rewrite->preg_index(2) . '&camera-brand=' . $wp_rewrite->preg_index(3)
        );
        $wp_rewrite->rules = $new_rules + $wp_rewrite->rules;
    }
    add_action( 'generate_rewrite_rules', 'eg_add_rewrite_rules' );
    

    network-camera, camera series and camera brands are three taxonomies i created. what i want is to change the url
    http://www.example.com/?camera-type=xx&camera-series=xx&camera-brand=xx

    to
    http://www.example.com/camera-type/xx/camera-series/xx/camera-brand/xx

    please help me with this.
    Thanks,

    • Brent says:

      What are you seeing to make you think this isn’t working?

      From a brief glance, the code looks OK (except that in your example URL you refer to a “camera-type” rewrite var which isn’t anywhere in your code). You might also want to make the regex less greedy – see this comment for details.

      Also, to help you debug rewrites, checkout Monkeyman Rewrite Analyzer.

      • Shafraz says:

        Thanks for replying,
        yes the example url i mentioned was wrong sorry about that.
        i installed the analyzer it is showing the rule i created on the top but when i search the url format
        network-camera/xx/camera-series/xx/camera-brand/xx

        it is triggering the taxonomy rule which was predefined “network-camera/(.+?)/?$”
        is there a way to override this issue.
        thanks,

        • Brent says:

          That could mean a few things, most likely that:

          • there is an error in your regex (though it looks OK)
          • the taxonomy rule is capturing *before* the multi-taxonomy rule (but based on your code and that you said Monkeyman is “showing the rule i created on the top”, I don’t think this is your problem); or
          • the taxonomy rule regex is too greedy (but it should be OK with (.+?))

          So you’ll have to experiment with your regex and the regex rule for the other taxonomy until Monkeyman gives you what you want. :)

  27. marcefx says:

    Thanks a million for such an awesome explanation! I’m using it now for a custom query with two taxonomies (Woocommerce attributes).

    Also, I’d appreciate it if you could point me in the right direction with an URL permalink structure. I need the URL for my custom post type (product) to look like:

    Site.com/taxonomy1_term/taxonomy2_term/postname/

    Do you think that’s doable?

    Thank you

    • Brent says:

      Hi marcefx, doing a permalink structure without prefixes gets difficult. If you look at how WordPress interprets the rewrites, it needs the prefix to know what object type you are querying.

      I have done something like this before though, and the way I decided to do it was to create rewrite rules with all taxonomy terms explicitly defined.

      For example, for a taxonomy eg_colour with the terms red, white, blue and a taxonomy eg_size with the terms small, medium and large, you’d need rewrites like this:

      function eg_add_rewrite_rules() {
          global $wp_rewrite;
       
          $new_rules = array(
              '(red|white|blue)/(small|medium|large)/(.+)/?$' => 'index.php?eg_colour=' . $wp_rewrite->preg_index(1) . '&eg_size=' . $wp_rewrite->preg_index(2) . '&postname=' . $wp_rewrite->preg_index(3)
          );
          $wp_rewrite->rules = $new_rules + $wp_rewrite->rules;
      }
      add_action( 'generate_rewrite_rules', 'eg_add_rewrite_rules' );
      

      Naturally, you’d query the taxonomy and dynamically add the terms so it’s not hardcoded, but I’ve hardcoded it hear to show the result you need to end up with.

      There are all sorts of issues and further considerations when doing this too, for example:

      • there is potential for rewrite conflict, if you have a taxonomy term or post name that matches that of another taxonomy’s term;
      • you need to regenerate the rewrite rules whenever a new taxonomy term is added;
      • and if you have a large number of taxonomy terms, your rewrite rules become unweidly and very slow.

      Good luck!

      • marcefx says:

        Thanks, Brent :-) I guess you’re right. I was able to rewrite the URL, but it’s causing really weird issues. My pagination is not working, and the blog posts went 404.

        So, I decided to use regular categories and the /%category%/%postname%/ structure. I’m going to try to use the same term for category1 and taxonomy1. I will add a “Noindex” rel to all categories and see what happens…

        Cheers!

  28. Hello,
    Great article! Thanks a lot.. will definitely use this!

    Is it possible to specify a pretty URL for taxonomy where it doesn’t matter what term within that taxonomy is used as long as a post is associated with that taxonomy. I hope that makes sense.

    I’ve tried couple of rules:

    1. add_rewrite_rule( ‘^shop/labels/?$’, ‘index.php?post_type=product&taxonomy=label’, ‘top’ );
    2. add_rewrite_rule( ‘^shop/labels/?$’, ‘index.php?post_type=product&label’, ‘top’ );

    However, none of them are working. It works fine if I specify an exact term for the taxonomy. However, there are a lot of terms for that taxonomy… the only thing I’d like to do is to get all the products that have ‘label’ taxonomy, no matter what exact term from ‘label’ taxonomy is being used.

    Any ideas how I can get that? I would hugely appreciate any tips
    Thanks,
    Dasha

    • Brent says:

      Hi Dasha,

      I’m not sure. Your first rewrite looks correct for achieving something like that, but I’m not sure if WP_Query will handle it correctly.

      The only way to *really* find out would be to go through the core code in WP_Query and see how it builds the CPT archive query (i.e. when the ‘post_type’ query arg is set) and see if there is also a way to have a sort of “has_tax” argument instead of a “taxonomy” equals argument.

      Brent

      • Hi Brent,
        Thanks for your reply. I’ve looked at the code of WP_Query class more and couldn’t figure it out :( doh… I think it might be impossible to do with WP as it is. Would be great to know how to do such thing… will look again in a bit when I have more time (probably couple of weeks sadly).

        If anyone knows or find out, let me know :)
        Thanks! Dasha

  29. Jared says:

    Hi Brent, amazing post and thanks very much. I have a question, if you are able to help us. If this is too complex for a quick answer are you available for hire?

    We have a WordPress site for reviewing local businesses. We are using the plugin WP No Taxonomy Base … in order to keep URLs cleaner.

    Therefore we have as follows:
    - example.com/restaurants/ … (instead of example.com/type/restaurants/)
    - example.com/chicago/ … (instead of example.com/region/chicago/)

    We are hoping to support these pretty URLs as follows:
    - example.com/restaurants/chicago/
    - example.com/hotels/chicago/

    The taxonomies are called “type” and “region” respectively, and the custom post type is called “property” posts. Can you tell us how to generate these pretty URLs properly, without the taxonomy bases showing up?

    I will keep checking back here to see if you reply. Thank you for the very informative piece and for helping out the WordPress community.

    • Brent says:

      Hi Jared, creating permalinks without a taxonomy prefix makes things difficult. I’ve touched on an answer in this comment. I’m not available for hire to implement it, but if you point a WordPress developer to this post and that comment, that should get them started. :)

  30. Pingback: Tweet Parade (no.44 Oct-Nov 2012) | gonzoblog

  31. evan says:

    This post is excellent and exactly what I am trying to accomplish. I am not a developer but can read code somewhat and get my way around wordpress. I have a couple questions.

    I have a current post type dealer. I have a brands and state taxonomy.

    My current permalink looks like this xyz.com/dealers/brands/honda/ and xyz.com/dealers/brands/toyota/

    I am trying to rewrite; xyz.com/dealers/brands/honda/?state=texas and xyz.com/dealers/brands/toyota/?state=texas

    Can you tell me how to modify your step 1 code and Step 2 code for my situation above?
    Secondly is it ok if I place these codes in my themes functions.php file?

    Lastly, you mention “Once you have added the rules and hooked to generate_rewrite_rules, you must visit the Permalink admin page to have WordPress call the generate_rewrite_rules hook.”

    Can you explain exactly what step I need to take in order to accomplish this?

    Thank you again for the great post!!!!

    • Brent says:

      Hi Evan,

      Can you tell me how to modify your step 1 code and Step 2 code for my situation above?

      All the information you need to get to what you want is available in this post and the WordPress Codex. It might just take you longer to get there without being an experienced WordPress programmer.

      If I tell you how to modify it, you won’t learn anything and I’d be giving your free custom web development (and if I did that for everyone who asked, I’d be broke and completely without sleep).

      Secondly is it ok if I place these codes in my themes functions.php file?

      Yep that’s fine.

      Can you explain exactly what step I need to take in order to accomplish this?

      Visit the Permalink admin page.

      • degmsb says:

        Ok, thank you I understand. Is there anyway you can tell me what the
        ?post_type=eg_event means? That was the main part I was confused on. I see you have a custom post type named events, but how does that relate to eg_event in your rewrite. Thanks!

        • Brent says:

          The eg_event is the name of the post type, that is, the value used in the $post_type parameter in the register_post_type() call.

          So you have a post type dealer, including post_type=dealer would filter posts to show only those which are the dealer type (assuming that dealer is the value you used for the $post_type parameter in your register_post_type() call).

          Hope that helps! :)

      • degmsb says:

        It has taken a while, but I finally have the working rewrite I was looking for based on your idea. I did learn a lot in the process, thank you for not giving me the answer! However, there is just one part of the code I am just not certain what is doing. It involves the $wp_rewrite->preg_index(1) or $wp_rewrite->preg_index(2). Can you give me a brief understanding of what these are doing. Thanks!

        • Brent says:

          It is returning a matches from the regular expression.

          It will probably take a bit of an understanding of regex to understand what that means. Reading up on the preg_match function should help.

          Basically, the first part of a rewrite rule can match parts of the URL by including two parentheses (). The preg_index() function is can then be used to access those matched pieces.

  32. Pingback: WP_Query详解(译自Wordpress官方资料) _ 蓝色妖姬

  33. mikemooney1 says:

    Hey great post. However i have a rather specific custom solutuion i require and was wondering whether you could guide me in the right direction. I have a site that has a custom post type list / archive that is filtered by a custom post type taxonomy and follows the below url structure http://www.example.name/page-one/page-two/events/cat/taxonomy-term. Where page-one is a standard wordpress post = page, page-two is a standard wordpress post = page, events is a standard wordpresds post = page. The events page has a custom template attached that filters the custom post type by taxonomy term passed in the URL. I have this working without the Pretty URL in the format http://www.example.name/?p=123&cat=term-slug. Im having trouble translatng this into the correct re-write rule. See my below code – it directs me to the right page but doesn’t append the /cat/taxonomy-term to the URL so i cant grab the end term to use as a filter in my custom query. Is there anything im missing

    function eg_add_rewrite_rules() {
        global $wp_rewrite;
    
        $new_rules = array(
            ‘(.+?)/(.+?)/evenements/cat/(.+?)/?$’ => ‘index.php?pagename=events&cat=’ . $wp_rewrite->preg_index(3)
        );
    
        $wp_rewrite->rules = $new_rules + $wp_rewrite->rules;
    
    }
    add_action( ‘generate_rewrite_rules’, ‘eg_add_rewrite_rules’ );
    
    function eg_add_query_vars( $query_vars ) {
        $new_vars = array( ‘cat’);
    
        return array_merge( $new_vars, $query_vars );
    }
    add_filter( ‘query_vars’, ‘eg_add_query_vars’ );
    

    i have the rewrite analyzer installed – its top and catches the pagename and cat var. Pls help!!!

    • Brent says:

      The rewrite code looks OK from first glance. When you say:

      doesn’t append the /cat/taxonomy-term to the URL so i cant grab the end term to use as a filter in my custom query.

      Do you mean the cat value is not being set in the $wp_query params?

      You shouldn’t have to do a custom query if the rewrites are working.

      Paste the code you’re using in your custom query and explain which file/hook it is being run in.

      • mikemooney1 says:

        OK – heres the first part that is used for the category filter – it highlights as selected when returned to the page

        $cat_slug = ''; // TODO: parse cat slug from the pretty URL
        
        // get term
        $term = get_term_by( 'slug', $cat_slug, "$custom_post_type-category" );
        $terms = array($term);
        
        ?>
        
         "$custom_post_type",
                    'child_of'                 => 0,
                    'parent'                   => '',
                    'orderby'                  => 'name',
                    'order'                    => 'ASC',
                    'hide_empty'               => 1,
                    'hierarchical'             => 1,
                    'exclude'                  => '',
                    'number'                   => '',
                    'taxonomy'                 => "$custom_post_type-category",
                    'pad_counts'               => false 
                );
        
            //get all categories
            $categories = get_categories( $args );
            
            if ($categories):
        ?>
            
                
                
                    <?php
        
                        //clean url
                        $url = get_current_url();
                        //$url = remove_query_part($url, 'paged');  //TODO: removed unwanted items from parsed URL
                        //$url = remove_query_part($url, 'cat');
                        //$url = remove_page_part( $url ) ;
                        $html = '';
        
                        foreach ($categories as $category) {
                            
                            $class = ($catid == $category->cat_ID) ? 'class="selected"' : '';                
                            $link = $url . '/cat/' . $category->slug;
                            $html .= ''.$category->cat_name.'';
                        }
        
                        $html .= '';
                        echo $html;
        
                    ?>
        

        The second part runs the custom query that populates the archive list

        $posts_per_page = get_option('posts_per_page');
        
        if ($terms) {
        
            foreach ($terms as $term){
                $catid[] = $term->term_id;
            }  
                    
        }
        
        // pushed category
        if ($catid) {
            
            $query = array(
                'post_type'      => "'$custom_post_type'",
                'tax_query' => array(
                        array(
                                'taxonomy'  => "$custom_post_type-category",
                                'field' => 'id',
                                'terms' => $catid 
                        )
                ),
                'sort_column'    => 'post_date',
                'order'          => 'DESC',
                'posts_per_page' => $posts_per_page,
                'paged'          => $paged
            );  
            
        } else {
            
            $query = array(
                'post_type'      => "'$custom_post_type'",
                'sort_column'    => 'post_date',
                'order'          => 'DESC',
                'posts_per_page' => $posts_per_page,
                'paged'          => $paged
            );
        };
            
        // custom post objects
        $customPosts = new WP_Query($query);
        
        
  34. Adam Lang says:

    Interesting stuff. Do you know if it’s possible, instead of saying ‘if this post contains a or b in taxonomy x’, to say, ‘if this post contains a in taxonomy x OR a in taxonomy y’ in a URL? (So, for example, if I want to search both article text and tags for the word ‘puppy’, in one URL?)

    • Brent says:

      It’s possible to run that kind of query. The Taxonomy Parameters section of the WP_Query codex article has an example at the end which selects posts that are in the quotes category OR have the quote format using the tax_query operator field.

      $args = array(
      	'post_type' => 'post',
      	'tax_query' => array(
      		'relation' => 'OR',
      		array(
      			'taxonomy' => 'category',
      			'field' => 'slug',
      			'terms' => array( 'quotes' )
      		),
      		array(
      			'taxonomy' => 'post_format',
      			'field' => 'slug',
      			'terms' => array( 'post-format-quote' )
      		)
      	)
      );
      $query = new WP_Query( $args );
      

      To map that to a pretty permalink, you could probably add an operator parameter to the URL, so something like:

      example.com/events/location/australia/or/industry/technology/
      

      Note that sneaky little or in the middle there.

      • Adam Lang says:

        Thanks. I was hoping to find something already baked into existing URL parameters, so I didn’t have to spend the time learning enough about php and WordPress to do something like this, but I guess I’ll bite the bullet and expend the requisite brainpower. This, at least, provides a basic idea, which simplifies my task a great deal.

  35. Elena says:

    I have a long time problem and look forward for your help
    I have a custom taxonomy of “town” (it is no hierarchical like tags)
    My settings in Permalink admin page:
    /%town%/%category%/%postname%/
    So this is what I like to accomplish.
    mysite.com/town/moscow/category/shopping/ Display all posts in this category in Moscow.
    mysite.com/town/moscow/category/shopping/supermarket/ – Display all posts in this -child-category in Moscow.
    mysite.com/town/moscow/category/shopping/supermarket/ postname- Display the post.
    I’ve added the code in function.php

    function eg_add_query_vars( $query_vars ) {
        $new_vars = array( 'town', 'category' );
    
        return array_merge( $new_vars, $query_vars );
    }
    add_filter( 'query_vars', 'eg_add_query_vars' );
    
    function eg_add_rewrite_rules() {
        global $wp_rewrite;
     
        $new_rules = array(
            'town/(.+?)/category/(.+?)/?$' => 'index.php?town=' . $wp_rewrite->preg_index(1) . '&category_name=' . $wp_rewrite->preg_index(2)
        );
        $wp_rewrite->rules = $new_rules + $wp_rewrite->rules;
    }
    add_action( 'generate_rewrite_rules', 'eg_add_rewrite_rules' );
    

    The result
    mysite.com /town/moscow/category/shopping/ It doesn’t!!!!! 400 error
    mysite.com/town/moscow/category/shopping/supermarket/ – It doesn’t!!!!!400 error
    mysite.com/town/ moscow/category/shopping/supermarket/ postname– It doesn’t!!!!!400error
    It seems the sustem doesn’t understand %town%
    What is wrong?

    • Brent says:

      Paste the code used to register your custom taxonomy.

      • Elena says:

        This is my code:

        function add_custom_taxonomies_town() {
                register_taxonomy('town', 'post', array(
                    'hierarchical' => false,
                    'labels' => array(
                        'name' => _x( 'Town', 'taxonomy general name' ),
                        'singular_name' => _x( 'Тоwn', 'taxonomy singular name' ),
                        'search_items' =>  __( 'Search Town' ),
                        'all_items' => __( 'All towns' ),
                        'parent_item' => null,
                        'parent_item_colon' => null,
                        'edit_item' => __( 'Edit town' ),
                        'update_item' => __( 'Apdate town' ),
                        'add_new_item' => __( 'Add town' ),
                        'new_item_name' => __( 'New town' ),
                        'menu_name' => __( 'Town' ),
                    ),
        			'query_var' => 'town',
                    'rewrite' => array(
                        'slug' => 'town',
                        'with_front' => false,
                        'hierarchical' => false
                ));
        	
            }
            add_action( 'init', 'add_custom_taxonomies_town', 0 ); 
        
        • Brent says:

          It looks like you’ve got the right query_var, and the only thing I noticed is that you’re adding 'category' to the query vars when really, you don’t need to. You can change eg_add_query_vars() to this:

          function eg_add_query_vars( $query_vars ) {
              $new_vars = array( 'town' );
          
              return array_merge( $new_vars, $query_vars );
          }
          add_filter( 'query_vars', 'eg_add_query_vars' );
          

          Not sure that will fix the issue. Your best bet is to install Monkeyman’s Rewrite Analyzer and figure out what is and is not matching against your new rules.

          • Elena says:

            I took away the “category” from the code but wordpress doesn’t understand %town% and writes me a mistake: Bad request!Error 400.
            May be i’m wrong with the definition of my settings in Permalink admin page:
            /%town%/%category%/%postname%/?

          • Brent says:

            Notice I don’t mention Permalink settings anywhere in this article?

            You don’t actually need to set /%town%/%category%/%postname%/ for this to work, and to give you one less thing to test/worry about, I would set it back to a default like /%postname%/.

  36. elnakoroleva says:

    After the all work has done the links are working that way:
    mysite.com/town/moscow/category/shopping/ -works!
    mysite.com/town/moscow/category/shopping/supermarket/ – works!
    mysite.com/town/moscow/category/shopping/supermarket/ postname- does not work
    I’ve tried to solve the task with another option:

    add_filter('post_link', 'town_permalink', 10, 3);
    add_filter('post_type_link', 'town_permalink', 10, 3);
     
    function town_permalink($permalink, $post_id, $leavename) {
        if (strpos($permalink, '%town%') === FALSE) return $permalink;
         
            // Get post
            $post = get_post($post_id);
            if (!$post) return $permalink;
     
            // Get taxonomy terms
            $terms = wp_get_object_terms($post->ID, 'town');  
            if (!is_wp_error($terms) && !empty($terms) && is_object($terms[0])) $taxonomy_slug = $terms[0]->slug;
            else $taxonomy_slug = 'not-town';
     
        return str_replace('%town%', $taxonomy_slug, $permalink);}
    	
    	function wp_town_query( $query ) {
        if( isset( $query->query_vars['town'] ) ):
            if( $town = get_term_by( 'id', $query->query_vars['town'], 'town' ) )
                $query->query_vars['town'] = $town->slug;
        endif;
    }
    add_action( 'parse_query', 'wp_town_query' );
    
    

    In Permalink settings:
    town/%town%/category/%category%/%postname%/

    But it does not work.
    But how can i get the link of the following way
    mysite.com/town/moscow/category/shopping/supermarket/ postname
    for my posting?
    I’m sorry to bother you again!

    • Brent says:

      You’ll need to add a new rule. Something like (not sure if the regex is right):

      function eg_add_rewrite_rules() {
          global $wp_rewrite;
       
          $new_rules = array(
              'town/(.+?)/category/(.+?)/?$' => 'index.php?town=' . $wp_rewrite->preg_index(1) . '&category_name=' . $wp_rewrite->preg_index(2),
              'town/(.+?)/category/(.+?)/(.+?)/?$' => 'index.php?town=' . $wp_rewrite->preg_index(1) . '&category_name=' . $wp_rewrite->preg_index(2) . '+' . $wp_rewrite->preg_index(3),
          );
          $wp_rewrite->rules = $new_rules + $wp_rewrite->rules;
      }
      add_action( 'generate_rewrite_rules', 'eg_add_rewrite_rules' );
      

      But that will only work for example.com/town/moscow/category/shopping/supermarket/. Ideally, the regex should matche any level of child categories.

  37. Elena says:

    Thanks for your help, but it doesn’t work.
    I have installed Monkeyman’s Rewrite Analizer, and test my link:
    example.com/town/moscow/category/shopping/supermarket/postname
    On pattern: town/(.+?)/category/(.+?)/?$
    Analyzer show:
    Town:Moscow
    Category-name: /shopping/supermarket/postname
    On pattern:
    town/(.+?)/category/(.+?)/(.+?)/?$
    Analizer show:
    Town:Moscow
    Category-name: shopping supermarket/postname

    I tried to add another rule,

    'town/(.+?)/category/(.+?)/([^/]+)(/[0-9]+)?/?$' => 'index.php?town='. $wp_rewrite->preg_index(1) . '&category_name=' . $wp_rewrite->preg_index(2). '&name='. $wp_rewrite->preg_index(3).'&page='. $wp_rewrite->preg_index(4) ,
    

    But it doesn’t work.

    • Brent says:

      I can’t spend the time figuring out the correct regex, but just take a look at other rules (as it looks like you’ve done), read up a little on regular expressions and you’ll eventually get it sorted.

      As a hint, the regex I gave as an example is too greedy, it captures the forward slash. The regex you provided does include a workaround for that, but also tries to capture a number rather than just a category name. Good luck!

  38. Hothan says:

    Hi, is it possible to make rewrite rules as: site.com/listing/apartments/us/newyork instead of site.com/listing/propertytype/apartments/country/us/state/newyork?

  39. Hothan says:

    Hi, I am still find the way to config url as site.com/listing/apartments/us/newyork instead of site.com/listing/propertytype/apartments/country/us/state/newyork. Please help me if it is possible.

    Thanks

    • Brent says:

      Hi Hothan, have a read through all the comments as there is information about how this can be done (and why it is very difficult) amidst them.

      • Hothan says:

        Hi, thanks for your reply.
        The solution provided is quite limited. I have tried another solution, to explode all “/” things and check their’s taxonomy names. It is alright at this time, but it seem not based on wordpress rewrite guide, and might results high volume of mysql query when my database become large.

  40. Manuel Doll says:

    Hi Brent, thank you so much for this, was enourmously helpful for me!
    I’m trying to generate a filter in the sidebar like you have on http://eex.gov.au/events/
    The trouble is, that get_terms() outputs all taxonomy terms and not just the ones associated with at least one post of the current query.
    Would you mind revealing how you have managed that when I go to e.g. http://eex.gov.au/events/industry/clean-energy-and-renewables/ only those terms show up in the sidebar which are associated with at least one of the queried events? Even with the count behind each term?
    This is my current code:

    global $wp_query;
    
    foreach ( get_object_taxonomies( get_query_var('post_type')) as $tax_name ) {
        
    	
    	$terms = get_terms($tax_name);
     
         foreach ( $terms as $term ) {
    		 
    	   if ( strpos( $wp_query->query_vars[$tax_name], $term->slug ) === false ) {
           echo 'slug ).'">'.$term->name.''; }
    	   else { echo 'slug ).'">'.$term->name.''; }
         }
    	 
    }

    Thank you so much again!

    • Brent says:

      Hi Manuel, thats a post unto itself! If you contact me privately using the contact form I can share the actual code I used, but I don’t want to publish it here without changing it to be more generic.

      • jwaisres says:

        I’m really interested on this script. Could you share it, please?

        Another question… Google webmasters panel shows me a lot of indexing errors related to multiple taxonomies. It’s trying to indexing bad taxonomies term (which don’t exist and are not linked on taxonomies menu) or empty taxonomies url’s (f.e. in /brands/name_of_brand/products/name_of_product/ Google bot is indexing /brands/product/name_of_product).

        Is there any way to redirect users when taxonomy doesn’t exist or is empty?

        Thanks for your amazing snippets and do our life easier.

        • Brent says:

          Hi jwaisres,

          The code from the sidebar can be found in this gist and the relevant functions called by the sidebar are in this gist.

          Neither are “plugin-and-play”, meaning, you’ll have to know what you’re doing in PHP to get it to work for your site. :)

          Regarding Google webmasters, find out where the links to those pages are coming from (from memory Google webmasters will give you that info if you drill down) then remove them.

          • jwaisres says:

            Thanks a million! I have implemented your eg_update_term_counts function (combined with eg_get_filter_permalink) to do exactly what I needed.

            I did this function to ignore pagination when showing taxonomies. It’s not perfect but It works and could be useful: http://pastebin.com/BiXWTAUi

            I can’t thank you enough for your help, Brent.

          • Brent says:

            Cool, glad it all helped. Thanks for sharing your function to ignore pagination. :)

  41. Guaisen says:

    1 week trying to do this one with multiple dropdowns to filter custom post type by 3 taxonomies but no luck.

    Did anyone try it?

    Thanks.

  42. Pingback: Advanced taxonomy queries with pretty urls - Jesterland

  43. Sallana says:

    Hi Brett, I’m trying to wrap my mind around what you’ve written here, it seems like this is exactly what I need to understand to make the site I’m currently working on.. complete. The missing piece of the puzzle, so to speak.

    I don’t really want anything coded for me, because I do want to learn something, but would you be able to tell me if this will be able to achieve the results I’ve outlined here:

    http://wordpress.stackexchange.com/questions/102585/taxonomy-drill-down-plugin-help-hierarchical-queries-within-plugin

    It looks like you’ve done something similar with it, I’m having some trouble getting it to work properly, though.. granted I haven’t really studied it too closely. Some examples of using it in a loop would be immensely helpful, but you’ve really done a lot just by posting this article, so thanks! :)

    (my in-progress site with stupid complex drill-down: tunagaming.dyndns.org)

  44. Sallana says:

    … I know your name is Brent. Sorry, long day…

    • Brent says:

      Hi Sallana, I just had a look at your site but can’t see any drill down. Did you remove it?

      Regardless, this answer is a pretty good start.

      What you really want to do is customise the WP_Query depending on the page number being displayed (when the query is for the genre taxonomy). The taxonomy template is probably the easiest way to do that, and as Ravs suggested in his answer. Good luck!

      • Sallana says:

        I did try his advice… I got stuck on a couple things…

        It doesn’t carry over the selected taxonomy term… and when you query multiple taxonomies (ie: yourdomain.com/?taxonomy=term&taxonomy=term) it defaults to archive.php rather than pulling the template for the last term queried.

        Maybe I’m just stupid. =/

        The drilldown is on the site now, under games. It looks weird ’cause I’m trying to implement pretty permalinks with the code that I’ve used so it’s appending ugly stuff to the end of the pretty URL. heh.

        That I can fix. The way that I ended up doing it, is I wrote a PHP function to explode the URL into an array and I pick out terms from the URL and use those in my archive.php code… which is on that answer page now.

        I dunno if that’s a clean or good way to do it, I’d love to figure out the proper way but it’s a little aggravating.

        Thanks for your reply and help.

  45. John O says:

    Hi – I am so glad I discovered this post, it has solved a big problem i.e. how to filter my WP content by multiple taxonomies at the same time, and how to update links to reflect this.

    One question – the taxonomy filters apply to the main loop, but In addition to the main loop, I also have a custom query pulling out a post with a custom field of ‘featured’ (this post is then not duplicated in the main loop). This post is ignored by the filtering method above and so appears all the time regardless of the current filter – is there any way I can make WP ‘see’ it and treat it in the same way as the main loop?

    Thanks. :-)

    • Brent says:

      Hi John, if you want it to be part of the main loop, try using 'pre_get_posts' filter instead of the query_posts() function. There are a few great answers on StackExchange, like this one, which should help.

  46. John O says:

    Hi Brent – thanks for getting back to me – I’m not sure how to use pre_get_posts() correctly – can’t quite get my head around where/how to do things with it.

    At the moment I have a WP_Query at the top of the page pulling out the featured post, then the normal main query loop following on afterwards (watching not to duplicate the post in the WP_Query.

    The problem is that if I try to filter by taxonomies, it filters stuff in the main loop fine, but completely ignores the one at the top.

    I’ve tried to find examples of how to run the main loop twice, and example two here: http://wp.smashingmagazine.com/2009/06/10/10-useful-wordpress-loop-hacks/ seemed to be exactly what I wanted, but it didn’t alter the behaviour of my page at all. :-(

  47. Giu Tae K says:

    Excellent tutorial!

    In my case I’m not looking for this:
    “sitename/post-type/taxonomy/term/post-name”
    I only want the terms in url like this:
    “sitename/post-type/term/post-name”

    Is this posible with the code that you post? I’m trying to make it work but the post give me 404 errors…

  48. Excellent article, all work on WordPress 3.6.

    if post type ‘post’

     $new_rewrite_rule =  $post_type->rewrite['slug'] . '/';
    

    '/' <- dont need, else create wrong rules

  49. NoobMe says:

    Hi nice post, I have a question thought..
    What if you have this kind of url
    localhost/this/is/a/very/long/url.php
    How can you rewrite this to
    localhost/short/url.php
    Thanks :)

  50. Rec says:

    Hi,
    I don’t know how to use the Rewrite Rule Generator.
    Please, can you explain it to me?

  51. ikala ngita says:

    Hello,
    Works well for me, thanks for the tutorial, here is my code:

    /// Taxonomy rewriting
    function eg_add_rewrite_rules() {
        global $wp_rewrite;
     
        $new_rules = array(
            'produits/(destinations|types)/(.+?)/(destinations|types)/(.+?)/?$' => 'index.php?post_type=produits&' . $wp_rewrite->preg_index(1) . '=' . $wp_rewrite->preg_index(2) . '&' . $wp_rewrite->preg_index(3) . '=' . $wp_rewrite->preg_index(4),
            'produits/(destinations|types)/(.+)/?$' => 'index.php?post_type=produits&' . $wp_rewrite->preg_index(1) . '=' . $wp_rewrite->preg_index(2)
        );
        $wp_rewrite->rules = $new_rules + $wp_rewrite->rules;
    }
    add_action( 'generate_rewrite_rules', 'eg_add_rewrite_rules' );
    

    But one question :
    I put pagination inside my archive-produits.php, it works well too when i navigate to :
    http://www.mywebsite.com/produits/destinations/new-york

    but when i add the second taxonomy and term :

    http://www.mywebsite.com/produits/destinations/new-york/types/hotels/page/2
    it is broken and display nothing.

    Any ideas to put pagination with this.?

    • Brent says:

      Any ideas to put pagination with this.?

      You may need to add pagination to the rules. Not exactly sure what that will look like, but this and this may help. As always, Google is your friend. Good luck! :)

      • coinsvoyages says:

        Yeah, thanks for reply

        i did this with no success
        ‘produits/(destinations|types)/(.+?)/(destinations|types)/(.+?)/page/?([0-9]{1,})/?$’ => ‘index.php?post_type=produits&’ . $wp_rewrite->preg_index(1) . ‘=’ . $wp_rewrite->preg_index(2) . ‘&’ . $wp_rewrite->preg_index(3) . ‘=’ . $wp_rewrite->preg_index(4).’&paged=’.$matches[5],

        how do you think?

        • Brent says:

          Not sure, possibly the regex? Try Monkeyman’s rewrite analyzer to see which rules are being matched when you enter in a URL you’re trying to match.

          • coinsvoyages says:

            I used your permalink generator, it works like a charm, big up for u man.
            I have last question may be stupid?
            how to get url like :
            ww.webiste.com/post_type/taxonomy-term/taxonomy2-term2

            i mean change the “/” by “-”

            Thanks

          • Brent says:

            An easy question with no easy answer. :)

            You’d need to adjust the regex to match the dash-separated URL structure instead of matching the forward-slash separated URL structure. You can then pass the matched term values into the rewrite rules.

            Not an easy task!

  52. www.google.com says:

    If you have done your preliminary work, the dog should go
    down for you. Go ahead. Dog eaar care is imperative to making sure your dog does not suffer needlessly.

  53. George says:

    Hi, how would I be able to add the query variable year to the url as well? So I could filter &year=anything and get http://www.example.com/events/location/nsw/industry/web/year/anything

    • Brent says:

      It’s too complicated to do for you George, you’ll need to figure it out. Be sure to share those details in a blog post once you have to help others!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s