I am trying to create a menu using code in D7 (see my related question) and have found that these two Drupal API functions have different arrays for the $item variable. My intent is to create a three level menu hierarchy but with all of the associated paths being the same format /name/%/something so that I can use views to refine the content. In hook menu, the parents are defined using the Path name (eg. parent/child/something). In menu_save_link, the hierarchy appears to be established using the pid array value in $item. Hook_menu is called when my custom module is read by drupal and the menu links are established. What I am wondering is if menu_link_save, could be used to do the same thing but with the same path style for all links, rather than a parent-child pattern?

I have been trying to trace the logic between the two functions in the API, but have not found the link between them. My thinking is that menu_link_saveis used by hook_menu after some preprocessing and that I might be able to define a uniform paths and establish the hierarchy through the pid. Failing that, is there another way to alter the paths and preserve the hierarchy?

  • Is setting up this a one-time thing, or an ongoing task?
    – mpdonadio
    Commented Mar 8, 2012 at 13:46
  • It's a one time thing
    – Ashlar
    Commented Mar 8, 2012 at 14:55

2 Answers 2


I don't totally get what you are trying to do, but I think this will do what you want.

  • Use your hook_menu() to just set up the paths that Drupal needs to respond to. Don't actually add the paths to a menu.

  • Make a new menu in the UI.

  • Loop through the items you need and call menu_link_save()

This should get you started

$item = array(
    "link_path" => "some/path/to/a/page",
    "link_title" => "Some Title",
    "menu_name" => "menu-the-name-of-the-menu",
    "plid" => $plid,
    "router_path" => "some/path/to/a/page",

$sql = "
    SELECT menu_links.mlid
    FROM {menu_links} menu_links
    WHERE menu_name = :menu_name AND plid = :plid AND link_path = :link_path

$result = db_query($sql, array(
    ":menu_name" => "menu-the-name-of-the-menu",
    ":link_path" => $item["link_path"],
    ":plid" => $plid

$record = $result->fetchObject();  

if (isset($record->mlid) && $record->mlid > 0) $item["mlid"] = (int) $record->mlid;


When your loop is done, call menu_cache_clear_all().

This is the inner loop from a module I have to automagically build some menus for non-node pages. I also have the full function wired up via hook_menu, so people can rebuild as needed.

  • Thanks, this is very helpful. If I understand, by your first bullet item I would invoke hook_menu to assemble the menu hierarchy using the path to tell Drupal the parent child relationship by the segments of the path /parent/child/etc. and assign it the type of MENU_CALLBACK so that it does not actually construct an actual menu. To clarify my intent, I want to change the path for the item from /parent/child/... to /name/id# and still preserve the parent-child relationship. Based upon what your answer, it appears I can edit a menu item created by hook_menu using menu_link_save?
    – Ashlar
    Commented Mar 8, 2012 at 17:09
  • Putting it another way, I assume that hook-menu defines the parent in plid and not longer relies on the link_path so it can be changed without altering the parent-child relationship?
    – Ashlar
    Commented Mar 8, 2012 at 17:24
  • Yeah, I think you can do this. In my case, I was dealing with the auto taxonomy term pages that taxonomy_menu created, and then I have to arrange these inside another menu.
    – mpdonadio
    Commented Mar 8, 2012 at 17:24
  • That is exactly where I started. So in your case, you used Taxonomy_menu and then changed the link_path and menu name for the items? I also note that you are changing the $pid. Isn't the taxonomy hierarchy already in the the database from when hook-menu was called? Or did you need to change it for another reason?
    – Ashlar
    Commented Mar 8, 2012 at 17:41
  • 1
    In my cases, I needed to add them to a new menu. I messed with $plid b/c I needed to rework the parent/child thing in the new menu. Remember, menu_router sets up the callbacks, menu_links has the actual menu entries. It helps if you do some experiments and see the changes to both those tables via MySQL Query Browser (or similar tool).
    – mpdonadio
    Commented Mar 8, 2012 at 19:14

They're slightly different concepts to be honest; the items defined in hook_menu() are saved in the menu_router table, while menu_link_save() saves items to the menu_links table.

They come together in menu_rebuild():

  1. hook_menu() is invoked through the menu_router_build() function to get a list of router items
  2. The router items are saved to the database using _menu_router_save()
  3. The menu links are generated/updated using _menu_navigation_links_rebuild(); the menu router items are passed to this function to build the links.
  4. Finally the caches are cleared.

There doesn't seem to be any way to pass custom data from the router items to the links I'm afraid, and I don't think hook_menu() supports the sort of hierarchy you're trying to build natively.

  • Is there a way (hook) to access and modify the path after the menu items are created so that I can change the name and preserve the lineage?
    – Ashlar
    Commented Mar 7, 2012 at 22:00
  • I'm not sure if they'll get what you want but you might want to look at hook_menu_link_insert and hook_menu_link_update
    – Clive
    Commented Mar 7, 2012 at 22:11

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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