Take the 2-minute tour ×

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

- Ireland
- Spain
- Sweden
- Laos
- Thailand
- Vietnam


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?

share|improve this question

8 Answers 8

up vote 10 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.

share|improve this answer

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

share|improve this answer

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

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

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.

share|improve this answer
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
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
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

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;

    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);

share|improve this answer

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);
        $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>';
            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.

share|improve this answer

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.

share|improve this answer
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
This does not provide an answer to the question. To critique or request clarification from an author, leave a comment below their post. –  brasofilo Feb 11 '13 at 20:04
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

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;
share|improve this answer

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

<select name="lokalizacja" id="ucz">
            <option value="">Wszystkie lokalizacje</option>
            $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) :

                    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";



        <label>Wybierz rodzaj miejsca</label>
        <select name="rodzaj_miejsca" id="woj">
            <option value="">Wszystkie rodzaje</option>
            $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";                   
share|improve this answer
Please explain why that could solve the problem. –  toscho Mar 25 '13 at 10:00

Your Answer


