\hook_menu
function
Define menu items and page callbacks.

Define menu items and page callbacks.

This hook enables modules to register paths in order to define how URL requests are handled. Paths may be registered for URL handling only, or they can register a link to be placed in a menu (usually the Navigation menu). A path and its associated information is commonly called a "menu router item". This hook is rarely called (for example, when modules are enabled), and its results are cached in the database.

hook_menu() implementations return an associative array whose keys define paths and whose values are an associative array of properties for each path. (The complete list of properties is in the return value section below.)

Callback Functions

The definition for each path may include a page callback function, which is invoked when the registered path is requested. If there is no other registered path that fits the requested path better, any further path components are passed to the callback function. For example, your module could register path 'abc/def':

function mymodule_menu() {
  $items['abc/def'] = array(
    'page callback' => 'mymodule_abc_view',
  );
  return $items;
}
function mymodule_abc_view($ghi = 0, $jkl = '') {

  // ...
}

When path 'abc/def' is requested, no further path components are in the request, and no additional arguments are passed to the callback function (so $ghi and $jkl would take the default values as defined in the function signature). When 'abc/def/123/foo' is requested, $ghi will be '123' and $jkl will be 'foo'. Note that this automatic passing of optional path arguments applies only to page and theme callback functions.

Callback Arguments

In addition to optional path arguments, the page callback and other callback functions may specify argument lists as arrays. These argument lists may contain both fixed/hard-coded argument values and integers that correspond to path components. When integers are used and the callback function is called, the corresponding path components will be substituted for the integers. That is, the integer 0 in an argument list will be replaced with the first path component, integer 1 with the second, and so on (path components are numbered starting from zero). To pass an integer without it being replaced with its respective path component, use the string value of the integer (e.g., '1') as the argument value. This substitution feature allows you to re-use a callback function for several different paths. For example:

function mymodule_menu() {
  $items['abc/def'] = array(
    'page callback' => 'mymodule_abc_view',
    'page arguments' => array(
      1,
      'foo',
    ),
  );
  return $items;
}

When path 'abc/def' is requested, the page callback function will get 'def' as the first argument and (always) 'foo' as the second argument.

If a page callback function uses an argument list array, and its path is requested with optional path arguments, then the list array's arguments are passed to the callback function first, followed by the optional path arguments. Using the above example, when path 'abc/def/bar/baz' is requested, mymodule_abc_view() will be called with 'def', 'foo', 'bar' and 'baz' as arguments, in that order.

Special care should be taken for the page callback drupal_get_form(), because your specific form callback function will always receive $form and &$form_state as the first function arguments:

function mymodule_abc_form($form, &$form_state) {

  // ...
  return $form;
}

See @link form_api Form API documentation @endlink for details.

Wildcards in Paths

Simple Wildcards

Wildcards within paths also work with integer substitution. For example, your module could register path 'my-module/%/edit':

$items['my-module/%/edit'] = array(
  'page callback' => 'mymodule_abc_edit',
  'page arguments' => array(
    1,
  ),
);

When path 'my-module/foo/edit' is requested, integer 1 will be replaced with 'foo' and passed to the callback function. Note that wildcards may not be used as the first component.

Auto-Loader Wildcards

Registered paths may also contain special "auto-loader" wildcard components in the form of '%mymodule_abc', where the '%' part means that this path component is a wildcard, and the 'mymodule_abc' part defines the prefix for a load function, which here would be named mymodule_abc_load(). When a matching path is requested, your load function will receive as its first argument the path component in the position of the wildcard; load functions may also be passed additional arguments (see "load arguments" in the return value section below). For example, your module could register path 'my-module/%mymodule_abc/edit':

$items['my-module/%mymodule_abc/edit'] = array(
  'page callback' => 'mymodule_abc_edit',
  'page arguments' => array(
    1,
  ),
);

When path 'my-module/123/edit' is requested, your load function mymodule_abc_load() will be invoked with the argument '123', and should load and return an "abc" object with internal id 123:

function mymodule_abc_load($abc_id) {
  return db_query("SELECT * FROM {mymodule_abc} WHERE abc_id = :abc_id", array(
    ':abc_id' => $abc_id,
  ))
    ->fetchObject();
}

This 'abc' object will then be passed into the callback functions defined for the menu item, such as the page callback function mymodule_abc_edit() to replace the integer 1 in the argument array. Note that a load function should return FALSE when it is unable to provide a loadable object. For example, the node_load() function for the 'node/%node/edit' menu item will return FALSE for the path 'node/999/edit' if a node with a node ID of 999 does not exist. The menu routing system will return a 404 error in this case.

Argument Wildcards

You can also define a %wildcard_to_arg() function (for the example menu entry above this would be 'mymodule_abc_to_arg()'). The _to_arg() function is invoked to retrieve a value that is used in the path in place of the wildcard. A good example is user.module, which defines user_uid_optional_to_arg() (corresponding to the menu entry 'tracker/%user_uid_optional'). This function returns the user ID of the current user.

The _to_arg() function will get called with three arguments:

  • $arg: A string representing whatever argument may have been supplied by the caller (this is particularly useful if you want the _to_arg() function only supply a (default) value if no other value is specified, as in the case of user_uid_optional_to_arg().
  • $map: An array of all path fragments (e.g. array('node','123','edit') for 'node/123/edit').
  • $index: An integer indicating which element of $map corresponds to $arg.

_load() and _to_arg() functions may seem similar at first glance, but they have different purposes and are called at different times. _load() functions are called when the menu system is collecting arguments to pass to the callback functions defined for the menu item. _to_arg() functions are called when the menu system is generating links to related paths, such as the tabs for a set of MENU_LOCAL_TASK items.

Rendering Menu Items As Tabs

You can also make groups of menu items to be rendered (by default) as tabs on a page. To do that, first create one menu item of type MENU_NORMAL_ITEM, with your chosen path, such as 'foo'. Then duplicate that menu item, using a subdirectory path, such as 'foo/tab1', and changing the type to MENU_DEFAULT_LOCAL_TASK to make it the default tab for the group. Then add the additional tab items, with paths such as "foo/tab2" etc., with type MENU_LOCAL_TASK. Example:

// Make "Foo settings" appear on the admin Config page
$items['admin/config/system/foo'] = array(
  'title' => 'Foo settings',
  'type' => MENU_NORMAL_ITEM,
);

// Make "Tab 1" the main tab on the "Foo settings" page
$items['admin/config/system/foo/tab1'] = array(
  'title' => 'Tab 1',
  'type' => MENU_DEFAULT_LOCAL_TASK,
);

// Make an additional tab called "Tab 2" on "Foo settings"
$items['admin/config/system/foo/tab2'] = array(
  'title' => 'Tab 2',
  'type' => MENU_LOCAL_TASK,
);

Comments

kingandy’s picture

No, the "%wildcard" has been correctly replaced with the module name "mymodule". Function names cannot include punctuation.

If you do come across a typo in the docs please file an issue where it will receive the attention of the team responsible.

thlo7777’s picture

You might can't get effect just set weight for menu and sub menu until you clear cache.

cristiroma’s picture

Some of the examples above are using t() for title like 'title' => t('Title').
These will result in calling t() twice and you will end up with translated strings in the locales_source table because Drupal will call t(t('Title')).

phponwebsites’s picture

How to add class to MENU_NORMAL_ITEM in hook_menu?


function mymodule_coupon_menu() {
$menu_items['admin/mymodule'] = array(
'title' => 'mymodule',
'callback' => 'drupal_goto',
'page arguments' => array('admin/mymodule'),
'access arguments' => array('wandisco coupon'),
'weight' => 15,
'expanded' => TRUE,
'type' => MENU_NORMAL_ITEM,
);
return $items;
}
akalata’s picture

Options are a keyed array that follow the structure used for l() and url().

$menu_items['admin/mymodule'] = array(
...
  'options' = array(
    'attributes' => array(
      'class' => array('my class', 'my other class'),
    ),
  ),
);

Notes: Classes must be an array, and options are not recognized by all menu types.

alberto56’s picture

In some cases, to avoid the Object of class stdClass could not be converted to int , you might want to put 'page callback', 'page arguments', 'access callback', and 'access arguments' near the end of your item list. For example, this commit to the Nodequeue module was required to work around that notice.

kingandy’s picture

Looks like the important thing there is putting 'access callback' after 'page callback', and maybe 'access arguments' after 'access callback', but I could be wrong.

j. ayen green’s picture

1. Clear cache
2. Make sure your hook_menu ends with a return:
return $items;

yx142578’s picture

how to theme a menu page callback form

roynilanjan’s picture

According to https://api.drupal.org/comment/56808#comment-56808, though it works and drupal incorporate this access-control but conceptually seems not a good solution, because default access callback out-of-box handles the user_access and user_access have $account in signature which is a current login user, and every user assign to specific role/roles. So seems always better to use the permission attribute in the access arguments so we can manage the access-control-flow using the permission only & for that we do not need to use custom access callback.

Note: Custom access callback is applicable if we need to control more granular level e.g. we want access control on top of $node so we need to handle node_access and on that context it's helpful to write some access callback with custom logic using user_access & node_access

natuk’s picture

In this unlikely scenario:

  • you have a sub-site installation with the sub-directory name "example-name"
  • you have a custom module with the same name (i.e. "example-name")
  • you define this
    $items['example-name/test'] = array(
        'page callback' => 'drupal_get_form',
        'page arguments' => array('example-name_test'),
      );
    

then visiting example-name/example-name/test will give you a "page not found" message. The two names cannot be the same.

avinash_thombre’s picture

Good if we include the callback function in the code and write some valid code, this will not let complete white screen and ascertain the code written is working. e.g.-

function custom_example_menu() {
$items = array();

$items['admin/config/system/custom-example'] = array(
'title' => 'Custom Example',
'page callback' => 'customexample_page',
'access callback' => TRUE,
);

return $items;
}
/*
* include following call back page
*/
function customexample_page() {
return 'Hi, this custom example config page and code is working!';
}

Pages