Getting Child Menu Items from a Drupal Menu Tree
October 22, 2014

If you're looking for a well written and tested function to recursively retrieve all child menu items in Drupal 7, you've come to the right place. All you have to do is pass it a valid node path such as 'node/123'.

The terminology you use might differ. You might call them sub menu links, deeper level menu links, sub nodes, or links below a parent node in a menu tree, or whatever, you know you want a function that does the job for you.

Using all possible terminology combinations, and after spending hours searching for this functionality in different search engines, Drupal's APIs, Stack Overflow, and Drupal Answers, we couldn't find a clean and minimal function that does the job.

Hence, we wrote our own. Our function is simple. Give it a node path, and it will return a array of node ids representing all the nodes that are child, or sub-menu-items, of the current node. As simple as that. Our function also uses multiple checks and defensive programming as much as possible and hence is suitable to be called from your theme or your custom modules. If anything wrong happens, our function moves to the next item without throwing errors, and returns an empty array in the worst case or when the given node doesn't have child menu items.

With this function, you can easily load all nodes that are sub menu items to the given one with the node_load_multiple function and process those nodes programmatically as you wish.

Happy coding.



 * Returns node ids of all the child items, including children of children
 * on all depth levels, of the given node path. Returns an empty array
 * if any error occurs.
 * @param string $node_path
 * @return array
function sk_get_all_menu_node_children_ids($node_path) {
    //Stop and return an empty array if node path is empty
    if(empty($node_path)) {
        return array();
    //Init empty array to hold the results
    $nids = array();
    //Init parent keys. Check 'foreach' loop on parent keys for more info.
    $parent_keys = array('plid', 'p1', 'p2', 'p3', 'p4', 'p5', 'p6', 'p7', 'p8', 'p9');
    //Collect menu item corresponding to this path to begin updates.
    //Note: we couldn't find a way to get the sub-tree starting from this item
    //only and hence we had to get the whole menu tree built and then loop on
    //the current item part only. Not so bad considering that Drupal will
    //most probably have the whole menu cached anyway.
    $parent_menu_item = menu_link_get_preferred($node_path);
    //Stop and return empty array if a proper current menu item couldn't be found
    if(empty($parent_menu_item['menu_name']) || empty($parent_menu_item['mlid'])) {
        return array();
    //Init parent item mlid for easier usage since now we know it's not empty
    $parent_menu_item_mlid = $parent_menu_item['mlid'];
    //Build whole menu based on the preferred menu_name gotten from this item
    $menu = menu_build_tree($parent_menu_item['menu_name']);
    //Reset menu cache since 'menu_build_tree' will cause trouble later on after 
    //you call pathauto to update paths as it can only be called once. 

    //Init processing array. This will hold menu items as we process them.
    $menu_items_to_process = array();

    //First run to fill up the processing array with the top level items
    foreach($menu as $top_level_menu_item) {
        $menu_items_to_process[] = $top_level_menu_item;

    //While the processing array is not empty, keep looping into lower
    //menu items levels until all are processed.
    while(count($menu_items_to_process) > 0) {
        //Pop the top item from the processing array
        $mi = array_pop($menu_items_to_process);

        //Get its node id and add it to $nids if it's a current item child
        //Note that $parent_keys contains all keys that drupal uses to
        //set a menu item inside a tree up to 9 levels.
        foreach($parent_keys as $parent_key) {
            //First, ensure the current parent key is set and also mlid is set
            if(!empty($mi['link']['mlid']) && !empty($mi['link'][$parent_key])) {
                //If the link we're at is the parent one, don't add it to $nids
                //We need this check cause Drupal sets p1 to p9 in a way you
                //can easily use to generate breadcrumbs which means we will
                //also match the current parent, but here we only want children
                if($mi['link']['mlid'] != $parent_menu_item_mlid) {
                    //Try to match the link to the parent menu item
                    if($mi['link'][$parent_key] == $parent_menu_item_mlid) {
                        //It's a child, add it to $nids and stop foreach loop.
                        //Link_path has the path to the node. Example: node/63.
                        if(!empty($mi['link']['link_path'])) {
                            $nids[] = str_replace('node/', '', $mi['link']['link_path']);

        //Add its child items, if any, to the processing array
        if(!empty($mi['below']) && is_array($mi['below'])) {
            foreach($mi['below'] as $child_menu_item) {
                //Add child item at the beginning of the array so that when
                //we get the list of node ids it's sorted by level with
                //the top level elements first; which is easy to attain
                //and also useful for some applications; why not do it.
                array_unshift($menu_items_to_process, $child_menu_item);
    return $nids;

Drupal Development Open Source

Share this post

Written by
Mario Awad

Founder of SOFTKUBE, lead developer, and getting things done addict. Passionate about open source, user interface design, business development, and the tech world.

More about Mario Awad


A small team of experts developing simple, usable, and high-quality web solutions. We blog about business, entrepreneurship, web development, and technology.

More about us

Recent Posts

Custom Theme Migration from Drupal 9 to Drupal 10

How to Change the Most Recent Git Commit Message

How to Make Google Chrome Forget a Permanent HTTP 301 Redirect

Finding and Fixing Unintended Body Overflow to Remove Horizontal Scrollbars

View all posts

All Posts Categories

Business Cheat Sheets CLI Design Development Downloads Drupal Email Google Apps HID Keyboards Multilingualism Open Source Philosophy PHP Pointing Devices Productivity Quotes Science Security SEO Technology Thoughts Windows Zend Framework