I have a hierarchical taxonomy called 'geographical locations'. It contains continents on a first level, and then the countries for each one. Example :

Europe
- Ireland
- Spain
- Sweden
Asia
- Laos
- Thailand
- Vietnam

etc.

Using get_terms() I managed to output the full list of terms, but the continents get mixed up with the countries, in one big flat list.

How can I output a hierarchical list like above?

  • 2
    In case anyone needs a hierarchical CHECKLIST (not the question here but related for people building custom UI for hierarchical taxonomies), the best answer is to use wp_terms_checklist() with your custom taxonomy. – jerclarke Feb 23 '16 at 16:27

10 Answers 10

up vote 15 down vote accepted

Use wp_list_categories with the taxonomy argument, it's built for creating hierarchal category lists but will also support using a custom taxonomy..

Codex Example:
Display terms in a custom taxonomy

If the list comes back looking flat it's possible you just need a little CSS to add padding to the lists, so you can see their hierarchal structure.

  • Could this be reversed? Display children first.. – Arg Geo Mar 6 '16 at 7:57

I realise this is a very old question, but if you have a need to build up an actual structure of terms, this might be a useful method for you:

/**
 * Recursively sort an array of taxonomy terms hierarchically. Child categories will be
 * placed under a 'children' member of their parent term.
 * @param Array   $cats     taxonomy term objects to sort
 * @param Array   $into     result array to put them in
 * @param integer $parentId the current parent ID to put them in
 */
function sort_terms_hierarchicaly(Array &$cats, Array &$into, $parentId = 0)
{
    foreach ($cats as $i => $cat) {
        if ($cat->parent == $parentId) {
            $into[$cat->term_id] = $cat;
            unset($cats[$i]);
        }
    }

    foreach ($into as $topCat) {
        $topCat->children = array();
        sort_terms_hierarchicaly($cats, $topCat->children, $topCat->term_id);
    }
}

Usage is as follows:

$categories = get_terms('my_taxonomy_name', array('hide_empty' => false));
$categoryHierarchy = array();
sort_terms_hierarchicaly($categories, $categoryHierarchy);

var_dump($categoryHierarchy);
  • 1
    Really good! thanks! – vinigarcia87 Oct 17 '16 at 2:32
  • 2
    This is actually really good. I would change one thing: $into[$cat->term_id] = $cat; into $into[] = $cat; Having the ID of the term as the array key is annoying (you can't get the first element easily using the 0 key) and useless (you're already storing the $cat object and you can get the id using the term_id property. – Nahuel Mar 7 '17 at 15:03
  • If like me you're trying to apply this function to a sub-level of categories, you will need to pass in the ID of the level you're currently at for this to work. But work nicely it does, thanks @popsi. – Ben Everard Aug 16 at 10:19

I dont know of any function that does what you want but you can build up something like this:

<ul>
    <?php $hiterms = get_terms("my_tax", array("orderby" => "slug", "parent" => 0)); ?>
    <?php foreach($hiterms as $key => $hiterm) : ?>
        <li>
            <?php echo $hiterm->name; ?>
            <?php $loterms = get_terms("my_tax", array("orderby" => "slug", "parent" => $hiterm->term_id)); ?>
            <?php if($loterms) : ?>
                <ul>
                    <?php foreach($loterms as $key => $loterm) : ?>
                        <li><?php echo $loterm->name; ?></li>
                    <?php endforeach; ?>
                </ul>
            <?php endif; ?>
        </li>
    <?php endforeach; ?>
</ul>

I haven't tested this but you can see what I'm getting at. What the above code will do is give you only two levels

EDIT: ahh yes you can use wp_list_categories() to do what you after.

  • Actually this is quite useful, as I need to have custom links (with a GET param) on the term links, which doesn't seem possible with the wp_list_categories() way of doing it. – mike23 Apr 13 '11 at 15:04
  • 1
    Yes this method will give more control over your output. But you could do some nice bit of find and replace on the output of wp_list_categories() to add in your GET parameters. Or even better build a filter for the function to add in the bits you want. Don't ask me how you do that as I've not yet been able to get my head around it :( – Brady Apr 13 '11 at 15:19
  • 3
    I'd suggest using a custom category walker with wp_list_categories if you want greater control over the output, it'll make your code much more reusable.. – t31os Apr 13 '11 at 16:09

You can use wp_list_categories(), with a 'taxonomy' argument.

As I was looking for the same but to get terms of one post, finally I compiled this, and it works for me.

What it does :
• it gets all terms of a taxonomy name for a specific post.
• for a hierachical taxonomy with two levels (ex: level1:'country' and level2:'cities'), it creates a h4 with the level1 followed by an ul list of level2 and this for all level1 items.
• if the taxonomy is not hierarchical, it will create only an ul list of all items. here is the code (I write it for me so I tried to be as generic as I can but...) :

function finishingLister($heTerm){
    $myterm = $heTerm;
    $terms = get_the_terms($post->ID,$myterm);
    if($terms){
        $count = count($terms);
        echo '<h3>'.$myterm;
        echo ((($count>1)&&(!endswith($myterm, 's')))?'s':"").'</h3>';
        echo '<div class="'.$myterm.'Wrapper">';
        foreach ($terms as $term) {
            if (0 == $term->parent) $parentsItems[] = $term;
            if ($term->parent) $childItems[] = $term; 
        };
        if(is_taxonomy_hierarchical( $heTerm )){
            foreach ($parentsItems as $parentsItem){
                echo '<h4>'.$parentsItem->name.'</h4>';
                echo '<ul>';
                foreach($childItems as $childItem){
                    if ($childItem->parent == $parentsItem->term_id){
                        echo '<li>'.$childItem->name.'</li>';
                    };
                };
                echo '</ul>';
            };
        }else{
            echo '<ul>';
            foreach($parentsItems as $parentsItem){
                echo '<li>'.$parentsItem->name.'</li>';
            };
            echo '</ul>';
        };
        echo '</div>';
    };
};

So finally you call the function with this (obviously, you replace my_taxonomy by yours) : finishingLister('my_taxonomy');

I don't pretend it's perfect but as I said it works for me.

The following code will generate drop-down with terms, but also can generate any other element/structure by editing the $outputTemplate variable, and editing str_replace lines:

function get_terms_hierarchical($terms, $output = '', $parent_id = 0, $level = 0) {
    //Out Template
    $outputTemplate = '<option value="%ID%">%PADDING%%NAME%</option>';

    foreach ($terms as $term) {
        if ($parent_id == $term->parent) {
            //Replacing the template variables
            $itemOutput = str_replace('%ID%', $term->term_id, $outputTemplate);
            $itemOutput = str_replace('%PADDING%', str_pad('', $level*12, '&nbsp;&nbsp;'), $itemOutput);
            $itemOutput = str_replace('%NAME%', $term->name, $itemOutput);

            $output .= $itemOutput;
            $output = get_terms_hierarchical($terms, $output, $term->term_id, $level + 1);
        }
    }
    return $output;
}

$terms = get_terms('taxonomy', array('hide_empty' => false));
$output = get_terms_hierarchical($terms);

echo '<select>' . $output . '</select>';  

I had this problem and none of the answers here worked for me, for one reason or another.

Here is my updated and working version.

function locationSelector( $fieldName ) {
    $args = array('hide_empty' => false, 'hierarchical' => true, 'parent' => 0); 
    $terms = get_terms("locations", $args);

    $html = '';
    $html .= '<select name="' . $fieldName . '"' . 'class="chosen-select ' . $fieldName . '"' . '>';
        foreach ( $terms as $term ) {
            $html .= '<option value="' . $term->term_id . '">' . $term->name . '</option>';

            $args = array(
                'hide_empty'    => false, 
                'hierarchical'  => true, 
                'parent'        => $term->term_id
            ); 
            $childterms = get_terms("locations", $args);

            foreach ( $childterms as $childterm ) {
                $html .= '<option value="' . $childterm->term_id . '">' . $term->name . ' > ' . $childterm->name . '</option>';

                $args = array('hide_empty' => false, 'hierarchical'  => true, 'parent' => $childterm->term_id); 
                $granchildterms = get_terms("locations", $args);

                foreach ( $granchildterms as $granchild ) {
                    $html .= '<option value="' . $granchild->term_id . '">' . $term->name . ' > ' . $childterm->name . ' > ' . $granchild->name . '</option>';
                }
            }
        }
    $html .=  "</select>";

    return $html;
}

And usage:

$selector = locationSelector('locationSelectClass');
echo $selector;

I used @popsi code that was working really well and I made it a more efficient and easy to read:

/**
 * Recursively sort an array of taxonomy terms hierarchically. Child categories will be
 * placed under a 'children' member of their parent term.
 * @param Array   $cats     taxonomy term objects to sort
 * @param integer $parentId the current parent ID to put them in
 */
function sort_terms_hierarchicaly(Array $cats, $parentId = 0)
{
    $into = [];
    foreach ($cats as $i => $cat) {
        if ($cat->parent == $parentId) {
            $cat->children = sort_terms_hierarchicaly($cats, $cat->term_id);
            $into[$cat->term_id] = $cat;
        }
    }
    return $into;
}

Usage :

$sorted_terms = sort_terms_hierarchicaly($terms);

Be sure that hierarchical=true is passed to your get_terms() call.

Note that hierarchical=true is the default, so really, just be sure that it hasn't been overridden to be false.

  • Hi Chip, yes 'hierarchical' is 'true' by default. – mike23 Apr 13 '11 at 14:37
  • Can you provide a link to a live example of the output? – Chip Bennett Apr 13 '11 at 14:44
  • Commenting on an answer left almost two years ago? Really? Actually, it is a proposed answer, even if worded as a question. Shall I edit it to be a statement, rather than a question? – Chip Bennett Feb 11 '13 at 23:22
  • get_terms() will return a full list of the terms (as the OP stated) but not a hierarchical list showing parent / child relationship as requested. – jdm2112 Mar 17 '16 at 5:00

Here I have four level dropdown select list with hidden first item

<select name="lokalizacja" id="ucz">
            <option value="">Wszystkie lokalizacje</option>
            <?php
            $excluded_term = get_term_by('slug', 'podroze', 'my_travels_places');
            $args = array(
                'orderby' => 'slug',
                'hierarchical' => 'true',
                'exclude' => $excluded_term->term_id,
                'hide_empty' => '0',
                'parent' => $excluded_term->term_id,
            );              
            $hiterms = get_terms("my_travels_places", $args);
            foreach ($hiterms AS $hiterm) :
                echo "<option value='".$hiterm->slug."'".($_POST['my_travels_places'] == $hiterm->slug ? ' selected="selected"' : '').">".$hiterm->name."</option>\n";

                $loterms = get_terms("my_travels_places", array("orderby" => "slug", "parent" => $hiterm->term_id,'hide_empty' => '0',));
                if($loterms) :
                    foreach($loterms as $key => $loterm) :

                    echo "<option value='".$loterm->slug."'".($_POST['my_travels_places'] == $loterm->slug ? ' selected="selected"' : '').">&nbsp;-&nbsp;".$loterm->name."</option>\n";

                    $lo2terms = get_terms("my_travels_places", array("orderby" => "slug", "parent" => $loterm->term_id,'hide_empty' => '0',));
                    if($lo2terms) :
                        foreach($lo2terms as $key => $lo2term) :

                        echo "<option value='".$lo2term->slug."'".($_POST['my_travels_places'] == $lo2term->slug ? ' selected="selected"' : '').">&nbsp;&nbsp;&nbsp;&nbsp;-&nbsp;".$lo2term->name."</option>\n";



                        endforeach;
                    endif;

                    endforeach;
                endif;

            endforeach;
            ?>
         </select>
        <label>Wybierz rodzaj miejsca</label>
        <select name="rodzaj_miejsca" id="woj">
            <option value="">Wszystkie rodzaje</option>
            <?php
            $theterms = get_terms('my_travels_places_type', 'orderby=name');
            foreach ($theterms AS $term) :
                echo "<option value='".$term->slug."'".($_POST['my_travels_places_type'] == $term->slug ? ' selected="selected"' : '').">".$term->name."</option>\n";                   
            endforeach;
            ?>
         </select>
  • 2
    Please explain why that could solve the problem. – fuxia Mar 25 '13 at 10:00
  • I think the logic is that it's a related problem. I found this post trying to figure out how to get a category-style hierarchical checklist and am tempted to add an answer here now that I've figured it out. I won't though because as you point out it doesn't answer the OQ. – jerclarke Feb 23 '16 at 16:26

Your Answer

 
discard

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

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