I'm building a store with products organized with a hierarchal taxonomy. I'd like the paths for Views showing products to reflect the taxonomy hierarchy. For example, products/category-a/subcategory-a, products/category-b, etc. (In this case, products will only be a member of one category/term at a time, and terms will either have child terms or products associated with them, but not both - with the example paths above, products/category-a would list the child terms of the Category A term, but no products.)

I can build a view with a path like products/%, and that works just fine with products/category-b, but I can't figure out how to get that working with products/category-a/subcategory-a - it throws a 404. I've thought of implementing a Views alter hook to alter $args to only use the last argument, but the 404 is happening from a failure in menu_get_item() - before any Views code has a chance to run (and call alter hooks).

I can't think of any other pleasant ways to work around this, but surely I'm not the first one to want to try to implement a catalog like this… What's the most correct way to do it? (Ugh, I feel like such a site-builder n00b.)

1 Answer 1


2 ways to approach this:

Option 1: Custom module (preferred)

If you don't mind coding:

First, you'll need to create a view with a term contextual filter. It is also important to add "Has taxonomy term ID depth modifier" filter. This will ensure that if you pass the parent term ID, all items that are tagged in any of the parent's child terms will be included in the result.enter image description here

Your custom module will have to do a few things to make this work:

  1. Define menu items using the top-level terms. The menus will also accept child terms (sub-category) as optional parameters. In essence, you are defining menu items to be in this pattern - products/<category_id>/<[sub_category_id]>. Where sub_category_id is an optional parameter.
  2. Define a menu callback which programmatically embeds the view. If the sub-category is not available, use the parent in generating the view. If the sub-category is available, use that instead.

Sample code:


 * Implements hook_menu().
function example_menu() {
  $items = array();

  // Define the menu for each top-level category. Each menu will have an
  // optional parameter that can contain the sub-category (a term under
  // the parent category).
  foreach (example_product_categories() as $tid => $term) {
    $items['catalog/'.$tid.'/%'] = array(
      'type' => MENU_CALLBACK,
      'page callback' => 'example_product_catalog_page',
      'access callback' => TRUE,
      'page arguments' => array($tid, 2),

  return $items;

 * Page callback to generate the product catalog view.
 * @param $parent
 *   The tid of a top-level product category.
 * @param $child
 *   A n-tier level of a product category.
function example_product_catalog_page($parent, $child = NULL) {
  // @todo: Optional. Check that $child is a term under $parent.

  // If $child is defined, we use that to generate the product catalog.
  if ($child) {
    return views_embed_view('product_catalog', 'product_catalog_page', $child);
  else {
    // If $child is not defined, we use the $parent to generate the product
    // catalog. This will also show all records tag in any of the parent's
    // child terms.
    return views_embed_view('product_catalog', 'product_catalog_page', $parent);

 * Returns an array of top-level product categories (terms) keyed by the tid.
function example_product_categories() {
  $product_vocabulary = '<VOCABULARY_NAME>'; // The name of the vocabulary
                                             // that contains the categories.
  $categories = array();

  // Load the vocabulary using the name to get the vocabulary ID.
  // If you know the vocabulary ID, you can skip the loading of the vocabulary
  // object and proceed directly to the loading of the taxonomy tree.
  if ($vocabulary = taxonomy_vocabulary_machine_name_load($product_vocabulary)) {
    // Load a taxonomy tree with only the top-level terms.
    $categories = taxonomy_get_tree($vocabulary->vid, 0, 1);

  return $categories;

Option 2: 2 Views

This would involve creating:

  • 2 views, each with a page, or
  • 1 view with 2 pages

The trick is to define 1 page that will accept 1 parameter only and another page that will accept 2 parameters. For the page that will accept 2 parameters, you can choose to ignore the 1st parameter or put in some validation (e.g. taxonomy validation).

Another option: Combination of modules

Although I haven't tried this, you might want to look at a combination of the following modules:

I would imagine that this will take some custom codes particularly in creating a custom taxonomy path, which would involve implementing hook_taxonomy_menu_path(). The end-goal is to create a custom taxonomy path which would take the form of products/<parent-tid>/<[child-tid]> if the term has a parent or products/<parent-tid> if the term doesn't have a parent. The custom menus will then point to a custom view or views.

  • If I go the custom menu item route, do I really have to worry about depth and depth modification? I could just manually set the desired args myself, right? At any rate, I guess I'm kind of surprised there's no site-building-only way to do this. Dec 23, 2013 at 16:53
  • Yes, you can. I was assuming a scenario where you would need to also display all items that are tagged under the child terms of a parent term. That's what the depth modifier contextual filter is for. Just in case, the depth modifier doesn't require an argument, it's just there to make sure that items tagged using the child terms are also included in the result. Essentially, you are only passing 1 argument, which is the term ID. This is how I would approach the problem if I were in your place, but you might want to try a combination of modules.
    – angheloko
    Dec 24, 2013 at 4:12

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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