41 | Hierarchical Category List with Post Titles

g+

If your wordpress site is organized with categories, it might be very useful to have a template to show an organized list of all categories with the associated posts.

This would work similar to the category widget with the setting ‘hierarchical’ – only that for each category, all the post titles are listed with links to the individual post.

This is the code:

<?php
/*****************************************************************
*
* alchymyth 2011
* a hierarchical list of all categories, with linked post titles
*
******************************************************************/
// http://codex.wordpress.org/Function_Reference/get_categories

 foreach( get_categories('hide_empty=0') as $cat ) :
 if( !$cat->parent ) {
 echo '<ul><li><strong>' . $cat->name . '</strong></li>';
 process_cat_tree( $cat->term_id );
 }
 endforeach;

 wp_reset_query(); //to reset all trouble done to the original query
//
function process_cat_tree( $cat ) {

 $args = array('category__in' => array( $cat ), 'numberposts' => -1);
 $cat_posts = get_posts( $args );

 if( $cat_posts ) :
 foreach( $cat_posts as $post ) :
 echo '<li>';
 echo '<a href="' . get_permalink( $post->ID ) . '">' . $post->post_title . '</a>';
 echo '</li>';
 endforeach;
 endif;

 $next = get_categories('hide_empty=0&parent=' . $cat);

 if( $next ) :
 foreach( $next as $cat ) :
 echo '<ul><li><strong>' . $cat->name . '</strong></li>';
 process_cat_tree( $cat->term_id );
 endforeach;
 endif;

 echo '</ul>';
}
?>

The first step is to find all top level categories (generated in alphabetical order) – which is simply done with the wordpress function ‘get_categories()’ and by checking if the category has a parent.
Caveat: because the code is checking for top level categories only, you can’t directly use the parameters of ‘get_categories()’ (see the Codex, the mostly unread documentation of WordPress) without some customization of the top section of the code.

The second step is to show the titles of all available posts for each top level category, linked to the full individual post; done with ‘get_posts()’ and ‘get_permalink()’.

The third step is to find possible direct child categories for this (top) category; done with the ‘parent’ parameter of ‘get_categories()’.

Then step 2 and 3 are repeated until all possible categories are dealt with.

edit 11/06/2011: parameter corrected.

edit 10/02/2012: a slightly different code – without the post lists – for a nested hierarchical category list with links to the category archive and an edit link for the category: http://pastebin.com/TTvYPKPH – initiated by this WPSE question

This entry was posted in WordPress and tagged , , , , , . Bookmark the permalink.

32 Responses to Hierarchical Category List with Post Titles

  1. Georg comments:

    Thanks for the great code!

    It’s extremely helpful as I want to show a hierarchical list of categories including category descriptions (so wp_list_categories is of no use).

    Your code works like charm, but I got one question: is it possible to change the output to proper list nesting? Like that:

    <ul>
    <li></li>
    <li>
    <ul>
    <li></li>
    </ul>
    </li>
    <ul>

    Thanks in advance!

    Georg

    • michael replies:

      this code seems to be doing the proper nesting: http://pastebin.com/TTvYPKPH
      can you post your used code and post a link to your site to illustrate the output?

      • Georg replies:

        Dear Michael,
        thanks for your quick reply!!! The new code works, perfectfly nested lists ;-)!

        I tried to combine it with the one above to display the post titles instead of just a link with the number of posts but couldn’t make it work. To make it even worse, there custom post types which don’t show up yet.

        Furthermore I try to output links to categories, but only on third level. Category links on level 1 and 2 will only be needed to collapse/toggle the navigation.
        Here’s a rough example to illustrate what I have in mind:


        <ul>
        <li><a href="#">1. Category (0)</a>
        <ul>
        <li><a href="#">1.1 Category (0)</a>
        <ul>
        <li><a href="get_category_link">1.1.1 Category wt. link to category archive (2)</a>
        <ul>
        <li><a href="get_permalink( $post->ID )">Post 1</a></li>
        <li><a href="get_permalink( $post->ID )">Post 2</a></li>
        </li>
        </ul>
        </li>
        </ul>
        </li>

        Maybe, I just have to revise my navigation design… ;-)

        Cheers,

        Georg

  2. raju comments:

    thank you so much for a very good piece of code.
    I am using the code mixed with various other people in the thread to create accordion category menu with posts. Now I want to use the same code where I do not want to display the posts but have link to the category page.

    WHAT I HAVE RIGHT NOW

    category 1
    subcat1
    post1
    post2
    subcat2
    category2

    WHAT I WANT

    category 1
    subcat1
    subcat2
    category2

    THE CODE I AM USING IS


    <?php

    foreach( get_categories('hide_empty=0&include=8') as $cat ) :
    if( !$cat->parent ) {

    echo '<ul id="nav-side">';
    process_cat_tree( $cat->term_id );
    }
    endforeach;

    wp_reset_query(); //to reset all trouble done to the original query//

    function process_cat_tree( $cat) {
    $args = array('category__in' => array( $cat ), 'numberposts' => -1);
    $cat_posts = get_posts( $args );

    global $post;
    if( $cat_posts ) :
    foreach( $cat_posts as $menuPost ) :
    echo '<li class="child';
    if ( $menuPost->ID == $post->ID ) { echo ' current'; }
    echo '">';
    echo '<a href="' . get_permalink( $menuPost->ID ) . '">' . $menuPost->post_title . '</a>';
    echo '</li>';
    endforeach;
    endif;

    $next = get_categories('hide_empty=0&orderby=id&order=DESC&parent=' . $cat);

    $post_cats = array(); foreach(get_the_category($post->ID) as $post_cat) $post_cats[] = $post_cat->term_id;

    if( $next ) :
    foreach( $next as $cat ) :
    echo '<li' . (in_array($cat->term_id,$post_cats)?' class="active"':'') . '>';

    echo '<a href="' . get_category_link( $cat) . '">' . $cat->name . '</a>';

    echo '<ul class="sub-menu">';
    process_cat_tree( $cat->term_id );
    endforeach;
    endif;
    echo '</ul>
    </li>';
    }
    ?>

    • raju replies:

      DID FOLLOWING

      <?php

      foreach( get_categories('hide_empty=0&include=8') as $cat ) :
      if( !$cat->parent ) {

      echo '<ul id="nav-side">';
      process_cat_tree( $cat->term_id );
      }
      endforeach;

      wp_reset_query(); //to reset all trouble done to the original query//

      function process_cat_tree( $cat) {

      $next = get_categories('hide_empty=0&orderby=id&order=DESC&parent=' . $cat);

      $post_cats = array(); foreach(get_the_category($post->ID) as $post_cat) $post_cats[] = $post_cat->term_id;

      if( $next ) :
      foreach( $next as $cat ) :
      echo '<li' . (in_array($cat->term_id,$post_cats)?' class="active"':'') . '>';

      echo '<a href="' . get_category_link( $cat) . '">' . $cat->name . '</a>';

      echo '<ul class="sub-menu">';
      process_cat_tree( $cat->term_id );
      endforeach;
      endif;
      echo '</ul>
      </li>';
      }
      ?>

      ITS SHOWING ONLY CATEGORIES BUT THE LINKS DO NOT WORK…. :(

      • alchymyth replies:

        echo '<a href="' . get_category_link( $cat->term_id) . '">' . $cat->name . '</a>';

  3. squizeers comments:

    Ok I fixed it by doing following. it is giving me active state on the “SUBCAT1″ now I want the active state for “Category1″ also for the following .
    Category 1(still needed)
    subcat1(got active)
    post
    post(active)
    subcat2


    <?php

    foreach( get_categories('hide_empty=0&include=4') as $cat ) :
    if( !$cat->parent ) {
    echo '<ul id="nav-side">';
    process_cat_tree( $cat->term_id );
    }
    endforeach;

    wp_reset_query(); //to reset all trouble done to the original query
    //
    function process_cat_tree( $cat) {
    $args = array('category__in' => array( $cat ), 'numberposts' => -1);
    $cat_posts = get_posts( $args );

    global $post;
    if( $cat_posts ) :
    foreach( $cat_posts as $menuPost ) :
    echo '<li class="child';
    if ( $menuPost->ID == $post->ID ) { echo ' current'; }
    echo '">';
    echo '<a href="' . get_permalink( $menuPost->ID ) . '">' . $menuPost->post_title . '</a>';
    echo '</li>';
    endforeach;
    endif;

    $next = get_categories('hide_empty=0&orderby=id&order=DESC&parent=' . $cat);

    if(is_single()) { $post_cats = array(); foreach(get_the_category($post->ID) as $post_cat) { $post_cats[] = $post_cat->term_id; } };
    if( $next ) :
    foreach( $next as $cat ) :
    echo '<li' . ((is_single()&&in_array($cat->term_id,$post_cats))?' class="active"':'') . '>
    <a href="#">' . $cat->name . '</a>
    <ul class="sub-menu">';
    process_cat_tree( $cat->term_id );
    endforeach;
    endif;

    echo '</ul></li>';
    }
    ?>

  4. squizeers comments:

    Thnak you for the quick response but the solution didnt work. It may be that i dint know how to do it. I am not good at php…just started learning….
    following is how I added ur code

    <?php

    if(is_single()) { $post_cats = array(); foreach(get_the_categories($post->ID) as $post_cat) { $post_cats[] = $post_cat->term_id; } };

    foreach( get_categories('hide_empty=0&include=4') as $cat ) :
    if( !$cat->parent ) {
    echo '<ul id="nav-side">';
    process_cat_tree( $cat->term_id );
    }
    endforeach;

    wp_reset_query(); //to reset all trouble done to the original query
    //
    function process_cat_tree( $cat) {
    $args = array('category__in' => array( $cat ), 'numberposts' => -1);
    $cat_posts = get_posts( $args );

    global $post;
    if( $cat_posts ) :
    foreach( $cat_posts as $menuPost ) :
    echo '<li class="child';
    if ( $menuPost->ID == $post->ID ) { echo ' current'; }
    echo '">';
    echo '<a href="' . get_permalink( $menuPost->ID ) . '">' . $menuPost->post_title . '</a>';
    echo '</li>';
    endforeach;
    endif;

    $next = get_categories('hide_empty=0&orderby=id&order=DESC&parent=' . $cat);

    if( $next ) :
    foreach( $next as $cat ) :
    echo '<li' . ((is_single()&&in_array($cat->term_id,$post_cats))?' class="active"':'') . '>

    <a href="#">' . $cat->name . '</a><ul class="sub-menu">';
    process_cat_tree( $cat->term_id );
    endforeach;
    endif;

    echo '</ul></li>';
    }
    ?>

    but i get the following error

    sleepers & i at bay
    watercolor on paper, 5″ x 7″

    Fatal error: Call to undefined function get_the_categories() in /home/raju/public_html/project/cutetastrophe/wp-content/themes/cutetastrophe/sidebar-artplay.php on line 30

    • alchymyth replies:

      my mistake – the function is properly called get_the_category();
      i.e. change the one line to:
      if(is_single()) { $post_cats = array(); foreach(get_the_category($post->ID) as $post_cat) { $post_cats[] = $post_cat->term_id; } };
      http://codex.wordpress.org/Function_Reference/get_the_category

      • squizeers replies:

        Again an error
        Warning: in_array() [function.in-array]: Wrong datatype for second argument in /home/raju/public_html/project/cutetastrophe/wp-content/themes/cutetastrophe/sidebar-artplay.php on line 56
        2009

  5. squizeers comments:

    I added ur code as follows

    <?php if(is_single()) { $post_cats = array(); foreach(get_categories($post->ID) as $post_cat) { $post_cats[] = $post_cat->term_id; } }; ?>

    <?php

    foreach( get_categories('hide_empty=0&include=4') as $cat ) :
    if( !$cat->parent ) {
    echo '<ul id="nav-side">';
    process_cat_tree( $cat->term_id );
    }
    endforeach;

    wp_reset_query(); //to reset all trouble done to the original query
    //
    function process_cat_tree( $cat) {
    $args = array('category__in' => array( $cat ), 'numberposts' => -1);
    $cat_posts = get_posts( $args );

    global $post;
    if( $cat_posts ) :
    foreach( $cat_posts as $menuPost ) :
    echo '<li class="child';
    if ( $menuPost->ID == $post->ID ) { echo ' current'; }
    echo '">';
    echo '<a href="' . get_permalink( $menuPost->ID ) . '">' . $menuPost->post_title . '</a>';
    echo '</li>';
    endforeach;
    endif;

    $next = get_categories('hide_empty=0&orderby=id&order=DESC&parent=' . $cat);

    if( $next ) :
    foreach( $next as $cat ) :
    echo '<li' . ((is_single()&&in_array($cat->term_id,$post_cats))?' class="active"':'') . '>

    <a href="#">' . $cat->name . '</a><ul class="sub-menu">';
    process_cat_tree( $cat->term_id );
    endforeach;
    endif;

    echo '</ul></li>';
    }
    ?>

    but gives me an error


    Warning: in_array() [function.in-array]: Wrong datatype for second argument

  6. Pingback: Displaying Custom Post Types by Term | iNexi

    g+

    02/04/2012 | [...] This goal led me to what is probably a cleaner solution. The following code is adapted from Hierarchical Category List …

  7. sally comments:

    I am so glad I found your site and words of wisdom – I am a keen fan of your postings on wordpress.org site. this code above is exactly what I am looking for. I am making a child theme from TwentyEleven and pasted this into a page template file – is this where it should go or should I put it somewhere else.
    I want to show all child categories of ‘current vendors’(parent category) and their post titles only and NOT all categories. It works as I have it in this example but I am having to set div #vendors display none in style sheet. I know this is bad but it’s working – please advise.

    <div id="primary">
    <div id="content" role="main">
    <div class="vendors"><?php
    /*****************************************************************
    *
    * alchymyth 2011
    * a hierarchical list of all categories, with linked post titles
    *
    ******************************************************************/
    // http://codex.wordpress.org/Function_Reference/get_categories

    foreach( get_categories('hide_empty=0') as $cat ) :
    if( !$cat->parent ) {
    echo '<ul><li><strong>' . $cat->name . '</strong></li>';
    process_cat_tree( $cat->term_id );
    }
    endforeach;

    wp_reset_query(); //to reset all trouble done to the original query
    //
    function process_cat_tree( $cat ) {

    $args = array('category__in' => array( $cat ), 'numberposts' => -1);
    $cat_posts = get_posts( $args );

    if( $cat_posts ) :
    foreach( $cat_posts as $post ) :
    echo '<li>';
    echo '<a href="' . get_permalink( $post->ID ) . '">' . $post->post_title . '</a>';
    echo '</li>';
    endforeach;
    endif;

    $next = get_categories('hide_empty=0&parent=' . $cat);

    if( $next ) :
    foreach( $next as $cat ) :
    echo '<ul><li><strong>' . $cat->name . '</strong></li>';
    process_cat_tree( $cat->term_id );
    endforeach;
    endif;

    echo '</ul>';
    }
    ?></div>

    <?php // http://codex.wordpress.org/Function_Reference/get_term_by
    $cat = get_category(get_term_by('slug','current-vendors','category'));
    echo '<ul class="vendor_grid"><li><strong>' . $cat->name . '</strong></li>';
    process_cat_tree( $cat->term_id );?>
    </div><!-- #content -->
    </div><!-- #primary -->

    Thank you for all the advice you have posted on wordpress.org

  8. Klyde comments:

    Hello Alchymyth,
    Your site has helped me a lot, and I’m glad I was able to find you. It’s really nice to see someone helping others in this way. In your caveat beneath the code example you say that you can’t directly use the parameters of get_categories… This leads to my question.
    How would one go about displaying the category description for ALL categories in the list, directly beneath each category listing (before the list of individual posts).
    I hope you have the time to respond, if not, again, I fully appreciate the time you’ve spent on your site.

    • Klyde replies:

      I believe this worked… tell me if I’m right?

      <?php
      foreach( get_categories('hide_empty=0') as $cat ) :
      if( !$cat->parent ) {
      echo '<ul><li><strong>' . $cat->name . '</strong></li>';
      echo '<div class="cat-desc"><p>' . $cat->category_description . '</p></div>';
      process_cat_tree( $cat->term_id );
      }
      endforeach;

  9. Chris Laursen comments:

    Hi alchymyth . Thank you for this tutorial. I’ve used it with two changes – I copy-pasted your recent comment to show only categories/posts within my category ‘Topics’, and I replaced ‘strong’ with ‘a href=”#” class=”level1″‘ to add a bit of styling to the menu.

    Please see http://samanthazalaznick.com – The top menu of links is the menu generated by your code. Below that, seperated by the big pink line, is static html with how I want the final menu to look and function. There is different styling applied to different level ul’s and li’s and I’m not sure how to modify your code to get that effect. I was easily able to apply “level1″ to all the child categories but I don’t know how to increment that for the deeper subcategories. I imagine I need to have a variable that increments to make it go level1, level2, etc. but don’t know how to achieve that. I also want to apply a certain class to top-level ul’s (“menu noaccordion”) and then a different class to all subcategory ul’s (“noaccordion acitem indent”). The reason for the different classes is for the particular javascript I’m using to make the menus expandable.

    Otherwise your code has gotten me close to where I need to be. If you can help me in any way I would really appreciate it, let me know if you need more information on my site. Thank you!

    • alchymyth replies:

      Hi Chris,
      I am going to look into this in the next few days, and let you know how difficult this might be.

      • Thalia replies:

        Great article!I’m new to WordPress and have been tnyirg to figure out all that needs to be blocked or modified so I don’t have dup content this article actaully makes categories useful!! Gives me more pages in googles index, thanks.Now, what about tags?I haven’t yet found everything on your site, if you have a good article on making the tag usefule i.e. like you did with categories because doesn’t google also see tags the same way?If I have a tag, let us say widgets, then that will have duplicate content because every post about widgets will have a short summary and it doesn’t do much for optimizing that tag now does it.Haven’t seen anything on this Would love to hear your thoughts.Thanks,Charles

  10. Nicholas comments:

    I have been searching all over for something like this. Thank you! Works great!

    I do have a question though, how can one specify to only display posts and category titles from a specific category?

    For example: I have a category Past exhibitions, with sub-categories below that. I only want to display the category names and post titles from Past exhibitions and none of my other categories.

    • alchymyth replies:

      try:
      // http://codex.wordpress.org/Function_Reference/get_term_by
      $cat = get_category(get_term_by('slug','past-exhibitions','category'));
      echo '<ul><li><strong>' . $cat->name . '</strong></li>';
      process_cat_tree( $cat->term_id );

  11. Martino comments:

    Hello, very nice solution.

    I’m looking for something similar but with a specific taxonomy for a specific custom post type.
    Do you think your code is a good starting point to get what i need?

  12. Annie comments:

    I really want to use this code but have limited knowledge of WordPress. Should this code be added to any existing php file or saved as a separate file under twentyeleven theme?
    Regards
    Ann

  13. Gynciuz comments:

    Thank you alchymyth! I just wonder if there is a way to show posts only from child categories? Now it is duplicating post titles. :)
    Thank you again for sharing this.

    • alchymyth replies:

      at the moment, with ‘category__in’ in the arguments for get_posts(), the code already only gets posts which are directly in a category, not posts of the child categories of that category.
      i assume that your posts are in more than one category – one of which is the parent of the other – and that this is the reason for duplicate posts.
      duplicates are not filtered on purpose, to show the relationship between all posts and the categories; i.e. if a post has more than one category ticked, it will show more than once in the list.
      to answer your question ‘if there is a way to show posts only from child categories?’, one would need to know how your posts and categories are structured, and my direct answer is: ‘i just don’t know right now’.

  14. knottulf comments:

    Brilliant! This saved my day!

    Made one amendment to the “if ( $cat_posts )” section:

    global $post;
    if( $cat_posts ) :
    echo '<ul class="menuposts">';
    foreach( $cat_posts as $menuPost ) :
    echo '<li class="child';
    if ( $menuPost->ID == $post->ID ) { echo ' current'; }
    echo '">';
    echo '<a href="' . get_permalink( $menuPost->ID ) . '">' . $menuPost->post_title . '</a>';
    echo '</li>';
    endforeach;
    echo '</ul>';
    endif;

    Which identifies the active link(s) for styling.

    Wonder this functionality isn’t included in the standard WP package…

    • squizeers replies:

      I am using the code for multilevel accordion menu with category posts. With your method, I got the active state on the posts how can i get the active state for parent menu. For example,

      Category 1
      subcat1
      post
      post(active)
      subcat2

      In the above list, when the post is active, I want the subcat1 and Category1also be active.
      I have the following code.


      <?php

      foreach( get_categories('hide_empty=0&include=6') as $cat ) :
      if( !$cat->parent ) {
      echo '<ul id="nav-side">';
      process_cat_tree( $cat->term_id );
      }
      endforeach;

      wp_reset_query(); //to reset all trouble done to the original query
      //
      function process_cat_tree( $cat) {
      $args = array('category__in' => array( $cat ), 'numberposts' => -1);
      $cat_posts = get_posts( $args );

      global $post;
      if( $cat_posts ) :
      foreach( $cat_posts as $menuPost ) :
      echo '<li class="child';
      if ( $menuPost->ID == $post->ID ) { echo ' current'; }
      echo '">';
      echo '<a href="' . get_permalink( $menuPost->ID ) . '">' . $menuPost->post_title . '</a>';
      echo '</li>';
      endforeach;
      endif;

      $next = get_categories('hide_empty=0&orderby=id&order=DESC&parent=' . $cat);

      if( $next ) :
      foreach( $next as $cat ) :
      echo '
      <li>
      <a href="#">' . $cat->name . '</a><ul class="sub-menu">';
      process_cat_tree( $cat->term_id );
      endforeach;
      endif;

      echo '</ul></li>';
      }
      ?>

      • alchymyth replies:

        untested – possibly get the post’s categories before that code, something like
        <?php if(is_single()) { $post_cats = array(); foreach(get_the_categories($post->ID) as $post_cat) { $post_cats[] = $post_cat->term_id; } }; ?>
        then compare this array with whatever category is just output by the code;
        example:
        echo '
        <li' . ((is_single()&&in_array($cat->term_id,$post_cats))?' class="active"':'') . '>
        <a href="#">' . $cat->name . '</a><ul class="sub-menu">';

Leave a Reply

Your email address will not be published.

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>