1. drupal
    1. 6
    2. 8
    3. 7
6 menu_link_save(&$item)
7 – 8 menu_link_save(&$item, $existing_item = array(), $parent_candidates = array())

Saves a menu link.

After calling this function, rebuild the menu cache using menu_cache_clear_all().


$item An associative array representing a menu link item, with elements:

  • link_path: (required) The path of the menu item, which should be normalized first by calling drupal_get_normal_path() on it.
  • link_title: (required) Title to appear in menu for the link.
  • menu_name: (optional) The machine name of the menu for the link. Defaults to 'navigation'.
  • weight: (optional) Integer to determine position in menu. Default is 0.
  • expanded: (optional) Boolean that determines if the item is expanded.
  • options: (optional) An array of options, see l() for more.
  • mlid: (optional) Menu link identifier, the primary integer key for each menu link. Can be set to an existing value, or to 0 or NULL to insert a new link.
  • plid: (optional) The mlid of the parent.
  • router_path: (optional) The path of the relevant router item.

$existing_item Optional, the current record from the {menu_links} table as an array.

$parent_candidates Optional array of menu links keyed by mlid. Used by _menu_navigation_links_rebuild() only.

Return value

The mlid of the saved menu link, or FALSE if the menu link could not be saved.

Related topics

▾ 23 functions call menu_link_save()

book_admin_edit_submit in modules/book/
Handle submission of the book administrative page form.
MenuLinksUnitTestCase::assertMenuLinkParents in modules/simpletest/tests/menu.test
Assert that at set of links is properly parented.
MenuLinksUnitTestCase::createLinkHierarchy in modules/simpletest/tests/menu.test
Create a simple hierarchy of links.
MenuLinksUnitTestCase::testMenuLinkReparenting in modules/simpletest/tests/menu.test
Test automatic reparenting of menu links.
MenuLinksUnitTestCase::testMenuLinkRouterReparenting in modules/simpletest/tests/menu.test
Test automatic reparenting of menu links derived from menu routers.
MenuNodeTestCase::testMenuNodeFormWidget in modules/menu/menu.test
Test creating, editing, deleting menu links via node form widget.
MenuRouterTestCase::testMenuGetNames in modules/simpletest/tests/menu.test
Test menu_get_names().
MenuRouterTestCase::testMenuLinkOptions in modules/simpletest/tests/menu.test
Test menu link 'options' storage and rendering.
MenuTestCase::testMenu in modules/menu/menu.test
Login users, add menus and menu links, and test menu functionality through the admin and user interfaces.
menu_edit_item_submit in modules/menu/
Process menu and menu item add/edit form submissions.
menu_edit_menu_submit in modules/menu/
Submit function for adding or editing a custom menu.
menu_enable in modules/menu/menu.module
Implements hook_enable().
menu_link_maintain in includes/
Insert, update or delete an uncustomized menu link related to a module.
menu_node_save in modules/menu/menu.module
Helper for hook_node_insert() and hook_node_update().
menu_overview_form_submit in modules/menu/
Submit handler for the menu overview form.
menu_reset_item in modules/menu/menu.module
Reset a system-defined menu link.
shortcut_link_edit_submit in modules/shortcut/
Submit handler for shortcut_link_edit().
shortcut_set_customize_submit in modules/shortcut/
Submit handler for shortcut_set_customize().
shortcut_set_save in modules/shortcut/shortcut.module
Saves a shortcut set.
standard_install in profiles/standard/standard.install
Implements hook_install().
_book_update_outline in modules/book/book.module
Common helper function to handles additions and updates to the book outline.
_menu_delete_item in includes/
Helper function for menu_link_delete; deletes a single menu link.
_menu_navigation_links_rebuild in includes/
Helper function to build menu links for the items in the menu router.


includes/, line 3006

function menu_link_save(&$item, $existing_item = array(), $parent_candidates = array()) {
  drupal_alter('menu_link', $item);

  // This is the easiest way to handle the unique internal path '<front>',
  // since a path marked as external does not need to match a router path.
  $item['external'] = (url_is_external($item['link_path'])  || $item['link_path'] == '<front>') ? 1 : 0;
  // Load defaults.
  $item += array(
    'menu_name' => 'navigation', 
    'weight' => 0, 
    'link_title' => '', 
    'hidden' => 0, 
    'has_children' => 0, 
    'expanded' => 0, 
    'options' => array(), 
    'module' => 'menu', 
    'customized' => 0, 
    'updated' => 0,
  if (isset($item['mlid'])) {
    if (!$existing_item) {
      $existing_item = db_query('SELECT * FROM {menu_links} WHERE mlid = :mlid', array('mlid' => $item['mlid']))->fetchAssoc();
    if ($existing_item) {
      $existing_item['options'] = unserialize($existing_item['options']);
  else {
    $existing_item = FALSE;

  // Try to find a parent link. If found, assign it and derive its menu.
  $parent = _menu_link_find_parent($item, $parent_candidates);
  if (!empty($parent['mlid'])) {
    $item['plid'] = $parent['mlid'];
    $item['menu_name'] = $parent['menu_name'];
  // If no corresponding parent link was found, move the link to the top-level.
  else {
    $item['plid'] = 0;
  $menu_name = $item['menu_name'];

  if (!$existing_item) {
    $item['mlid'] = db_insert('menu_links')
      'menu_name' => $item['menu_name'], 
      'plid' => $item['plid'], 
      'link_path' => $item['link_path'], 
      'hidden' => $item['hidden'], 
      'external' => $item['external'], 
      'has_children' => $item['has_children'], 
      'expanded' => $item['expanded'], 
      'weight' => $item['weight'], 
      'module' => $item['module'], 
      'link_title' => $item['link_title'], 
      'options' => serialize($item['options']), 
      'customized' => $item['customized'], 
      'updated' => $item['updated'],

  // Directly fill parents for top-level links.
  if ($item['plid'] == 0) {
    $item['p1'] = $item['mlid'];
    for ($i = 2; $i <= MENU_MAX_DEPTH; $i++) {
      $item["p$i"] = 0;
    $item['depth'] = 1;
  // Otherwise, ensure that this link's depth is not beyond the maximum depth
  // and fill parents based on the parent link.
  else {
    if ($item['has_children'] && $existing_item) {
      $limit = MENU_MAX_DEPTH - menu_link_children_relative_depth($existing_item) - 1;
    else {
      $limit = MENU_MAX_DEPTH - 1;
    if ($parent['depth'] > $limit) {
      return FALSE;
    $item['depth'] = $parent['depth'] + 1;
    _menu_link_parents_set($item, $parent);
  // Need to check both plid and menu_name, since plid can be 0 in any menu.
  if ($existing_item && ($item['plid'] != $existing_item['plid'] || $menu_name != $existing_item['menu_name'])) {
    _menu_link_move_children($item, $existing_item);
  // Find the router_path.
  if (empty($item['router_path'])   || !$existing_item || ($existing_item['link_path'] != $item['link_path'])) {
    if ($item['external']) {
      $item['router_path'] = '';
    else {
      // Find the router path which will serve this path.
      $item['parts'] = explode('/', $item['link_path'], MENU_MAX_PARTS);
      $item['router_path'] = _menu_find_router_path($item['link_path']);
  // If every value in $existing_item is the same in the $item, there is no
  // reason to run the update queries or clear the caches. We use
  // array_intersect_assoc() with the $item as the first parameter because
  // $item may have additional keys left over from building a router entry.
  // The intersect removes the extra keys, allowing a meaningful comparison.
  if (!$existing_item || (array_intersect_assoc($item, $existing_item)) != $existing_item) {
      'menu_name' => $item['menu_name'], 
      'plid' => $item['plid'], 
      'link_path' => $item['link_path'], 
      'router_path' => $item['router_path'], 
      'hidden' => $item['hidden'], 
      'external' => $item['external'], 
      'has_children' => $item['has_children'], 
      'expanded' => $item['expanded'], 
      'weight' => $item['weight'], 
      'depth' => $item['depth'], 
      'p1' => $item['p1'], 
      'p2' => $item['p2'], 
      'p3' => $item['p3'], 
      'p4' => $item['p4'], 
      'p5' => $item['p5'], 
      'p6' => $item['p6'], 
      'p7' => $item['p7'], 
      'p8' => $item['p8'], 
      'p9' => $item['p9'], 
      'module' => $item['module'], 
      'link_title' => $item['link_title'], 
      'options' => serialize($item['options']), 
      'customized' => $item['customized'],
      ->condition('mlid', $item['mlid'])
    // Check the has_children status of the parent.
    if ($existing_item && $menu_name != $existing_item['menu_name']) {
    // Notify modules we have acted on a menu item.
    $hook = 'menu_link_insert';
    if ($existing_item) {
      $hook = 'menu_link_update';
    module_invoke_all($hook, $item);
    // Now clear the cache.
  return $item['mlid'];