Last updated 23 December 2012. Created on 5 January 2008.
Edited by dozymoe, frazras, vinmassaro, HongPong. Log in to edit this page.

Main topic described: Module settings
Drupal hook used: hook_menu

Now that we have a working module, we'd like to make it more flexible. If we have a site that has been around for a while, content from a week ago might not be as interesting as content from a year ago. Similarly, if we have a busy site, we might not want to display all the links to content created last week. So, let's create a configuration page for the administrator to adjust how many links to display, and leave it at that for this tutorial.

Create the configuration function

We'd like to configure how many links display in the block, so we'll create a form for the administrator to set the number of links. The first step in doing that is to define a "system settings" form page, using the Drupal Forms API. Since this is our "administer" page, we'll call the function that generates the function onthisdate_admin() (or we could choose another name, but it should start with onthisdate_ ) and put it into our onthisdate.module file:

function onthisdate_admin() {
  $form = array();

  $form['onthisdate_maxdisp'] = array(
    '#type' => 'textfield',
    '#title' => t('Maximum number of links'),
    '#default_value' => variable_get('onthisdate_maxdisp', 3),
    '#size' => 2,
    '#maxlength' => 2,
    '#description' => t("The maximum number of links to display in the block."),
    '#required' => TRUE,

  return system_settings_form($form);

There are several things to notice about this function:

  • $form is an array defining form elements in the Drupal Forms API. Each element of the array corresponds to a form element; in this case, we have one required two-character text field whose label is "Maximum number of links", with help text "The maximum number of links to display in the block".
  • We only have to define form elements for the actual settings elements -- the system_settings_form() function will take care of creating the form page, adding a submit button, and saving the settings.
  • Drupal maintains a database of "variables", or settings; each setting must have a unique name, so customarily the module name is used as a prefix -- our setting is called 'onthisdate_maxdisp'.
  • The Drupal function variable_get() is used to retrieve the previously-stored value of the setting, and we've given it a default value of 3 if there was no previously-stored value.
  • The setting name, 'onthisdate_maxdisp', is used in the call to variable_get() and is also the array key for the form element in the $form array. This is important, because the Drupal system_settings_form() function will use the array key as the name of the setting to save when the form is submitted.
  • All of the text our form will display is passed through the translate function of t(), so that sites in other languages can use our module.
  • Refer to Drupal Forms API Reference and Drupal Forms API Quickstart Guide for more detailed information on what more you can do with the Drupal Forms API.

We'll also need to modify our hook_block() implementation to use this setting. The best way to do that in Drupal is to use the db_query_range function:

  $limitnum = variable_get("onthisdate_maxdisp", 3);

  $query = "SELECT nid, title, created FROM " .
           "{node} WHERE created >= %d " .
           "AND created <= %d";

  $query_result = db_query_range($query, $start_time, $end_time, 0, $limitnum);

You'll need to replace the corresponding $query and $query_result lines in your existing onthisdate_block() function with these three lines.

Add the page to hook_menu

Once you have created the function with your settings form, you need to define a URL within Drupal for your settings page. This is done by implementing Drupal's hook_menu. In our hook_menu implementation, we will return an array which describes to Drupal which URL path to use, the title to display, the function to call to generate the page, and the permissions required.

We would like only administrators to be able to access this page, so we'll place the permissions check for the module here in hook_menu so that Drupal can itself check the appropriate permission. To minimize the number of permissions an administrator has to deal with, we're going to use the global administration permission for administrating our module instead of creating a new custom permission.

To implement hook_menu(), create a function called onthisdate_menu() and put it in your onthisdate.module file:

function onthisdate_menu() {

  $items = array();

  $items['admin/settings/onthisdate'] = array(
    'title' => 'On this date module settings',
    'description' => 'Description of your On this date settings page',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('onthisdate_admin'),
    'access arguments' => array('administer onthisdate settings'),
    'type' => MENU_NORMAL_ITEM,

  return $items;

Note that the array key 'admin/settings/onthisdate' is the URL that we are defining, and the array elements give the menu link title ("On this date module settings" -- it should always start with a capital letter and otherwise be lower-case), a longer description, the name of the function to call that will return the settings form ('onthisdate_admin'), and the access permission. You can check out the hook_menu documentation for more details.

After adding this to the module, you will need to clear the menu cache, so that Drupal will recognize the new URL (Drupal caches a lot of data, including a list of all the URLs it recognizes). To clear the cache, go to Administer >> Site Configuration >> Performance, scroll to the foot of the page, and click the "Clear cached data" button.

Now you can test the settings page by editing the number of links displayed and noticing that the block content adjusts accordingly. Navigate to the settings page: admin/settings/onthisdate or Administer » Site configuration » On this date. Adjust the number of links and save the configuration. The maximum number of links in the block should adjust accordingly.

Validate the user input

Although we aren't required to validate the user input, it is nice to do so. We can do this by writing a onthisdate_admin_validate function (in onthisdate.module) that checks whether the value the user entered is a number greater than 0. Because the validation function has the same name as the form generation function, with a "_validate" suffix, Drupal will use our validation function automatically when the form is submitted.

function onthisdate_admin_validate($form, &$form_state) {
  $maxdisp = $form_state['values']['onthisdate_maxdisp'];
  if (!is_numeric($maxdisp)) {
    form_set_error('onthisdate_maxdisp', t('You must enter an integer for the maximum number of links.'));
  elseif ($maxdisp <= 0) {
    form_set_error('onthisdate_maxdisp', t('Maximum number of links must be positive.'));

Now if you try to enter something that it doesn't like (a word, or a negative number), it will tell you to enter a correct value.

See Also

Looking for support? Visit the forums, or join #drupal-support in IRC.


javier.ortiz.llerena’s picture

I am a new user of Drupal and I hope I am not wrong saying this, but I believe that where it says:

After adding this to the module, you will need to clear the menu cache, so that Drupal will recognize the new URL (Drupal caches a lot of data, including a list of all the URLs it recognizes). To clear the cache, go to Administer >> Settings >> Performance, scroll to the foot of the page, and click the "Clear cached data" button.

It should say:

After adding this to the module, you will need to clear the menu cache, so that Drupal will recognize the new URL (Drupal caches a lot of data, including a list of all the URLs it recognizes). To clear the cache, go to Administer >> Site configuration >> Performance, scroll to the foot of the page, and click the "Clear cached data" button."

Since I have a new installation of Drupal (6.13) and it appears as "Administer >> Site configuration" instead of "Administer >> Settings".

Thank you,


defisko’s picture

I'm tryning to see this at my left menu and its not visible.
Module is enabled and working ( i can see message: Sorry No Content ) Any sugesstions ?

gstevens’s picture


"hook_menu() is called rarely, such as when modules are enabled. If you edit a module’s hook_menu(), you must visit admin/build/modules for the changes to take effect."

Ira Rabinowitz’s picture

If I enter an invalid input I get a red box and I can't save it but I don't get any error message.

Why am I not getting the error message?

madmanmax’s picture

You do get a red box, but it also contains a message depending on what invalid input you entered:
- Maximum number of links must be positive. (e.g. you enter "-2") or
- You must enter an integer for the maximum number of links (e.g. a string instead of an integer)

cwaeland’s picture

I'm having difficulty getting the menu link to appear. I have tried numerous things but nothing seems to work.

I have followed the advice in this article in regards to clearing the cache in the Administer >> Site Configuration >> Performance section.

I have cleared the cache manually by truncating the tables.

I have used the Devel module to clear the cache and rebuild the menu system.

I have tried several browsers making sure to clear their respective caches.

I have also disabled and enabled the module several times as well.

Does anybody know anything else I should try or can recommend some tools to debug this?


pudge’s picture

I was having the same problem. The settings page would appear but none of the options I had coded shows up.

I am not sure why this works but I was following the book "Pro Drupal Development" by John K. VanDyk and they used hook_admin_settings() instead of just hook_admin(). I followed their advice and it worked.

If someone can explain the difference please jump in.

Good Luck.

ksweet’s picture

For anyone else reading this, I don't think hook_admin_settings() should be used. Googling it made it look like an undocumented feature of 5.x. It doesn't even look to be deprecated--I can't really figure out why it's in there at all.

It took a lot of disabling and re-enabling of my module, lots of cache clearing, lots of looking at the code (I re-arranged it so that the hook_admin() and hook_menu were above hook_block(), not sure if that did anything but it worked afterward) but I finally managed to get it to work with hook_admin().


suntog’s picture

THANK YOU ksweet!

I read your advice and reloaded the entire tutorial from scratch. Then I looked at the "image.module" from my downloaded modules to see what location the "hook_menu" code was placed, which was right after "hook_access" and way before "hook_block". I placed "onthisdate_menu" just after "onthisdate_permissions" and then cleared my cache. Then finally everything worked (after much hair pulling, anguish and lamentation).

anjjriit’s picture

I have similar system settings form, but always return an error.
If you don't mind, please help me at

bealdav’s picture

If you deactivate theme developer from devel module, your menu is displayed

jppi_Stu’s picture

Try going to the settings page directly (i.e., /admin/settings/moduleName). Does it load, or does it give some sort of error that can shed some light on the problem? Does it give you an Access Denied message? Does it go to the main "Site configuration" page?

If it gives you an Access Denied message, check to see if you have any errors in the "access arguments" array element (in moduleName_menu()). (For example, I had a late-night typo in which I left out the '=' sign, so instead of having an array element with "access arguments" as the index and some usable value, I had a boolean value with [presumably] an ordinal index.) Anyway, if Drupal determines that you're not authorized to view the page, the menu link won't appear.

stevenaburton’s picture

I got to the place where the variable was successfully created and returned the correct value. However, the line $query_result = db_query_range($query, $start_time, $end_time, 0, $limitnum); did not limit the results as expected.

The syntax in the tutorial looks correct so I was unable to find a resolution using the db_query_range function but I worked around the problem by adding a line to the end of the query...

$query = "SELECT nid, title, created FROM " .
"{node} WHERE created >= %d " .
"AND created <= %d";
"LIMIT 0 , " . $limitnum;

This had the desired effect.

cbearhoney’s picture

You're right, the tutorial is missing the LIMIT in the modified query. However, you want to maintain the sql placeholders(%d) to avoid sql injections. So rewrite your queries to read:

$query = "SELECT nid, title, created FROM {node} WHERE created >= '%d' ".
"AND created <= '%d' LIMIT %d, %d";

$query_result = db_query($query, $start_time, $end_time, 0, $limitnum);

More on sql coding conventions here.

drupeo’s picture

The tutorial changed the function db_query to db_query_range, which does the limit stuff for you. Not taking anything away from you in your comments about placeholders though, spot on.

dontgotanick’s picture


i want to grant everyone who got my on created permission access to that page.

        function cwg_signup_email_perm() {
		return array('access signup_email');

I tried it like that, but it does not work it always tells me access restricted.

       function cwg_signup_email_menu() {

		$items = array();

		$items['admin/settings/cwg_signup_email'] = array(
		'title' => 'CWG-CancelMail-Signups',
		'description' => 'Set Cancel Mail of active Event.',
		'page callback' => 'drupal_get_form',
		'page arguments' => array('cwg_signup_email_admin'),
		'access arguments' => array('access signup_email'),
		'type' => MENU_NORMAL_ITEM,

		return $items;

Anyone an idea.

Greetz dave

behestee’s picture

You missed the hook name: it will hook_permission
Please refer:!system!system.api.php/function...

smiiith’s picture

I got this code working, but I don't understand the purpose of the hook_block implementation. Everythings seems to work without it. What is that function for?

Morten Najbjerg’s picture

Updated the page: wrapped title and description tag in t() function for best practice.

zadeluca’s picture

If I put something invalid in, say -5, and then click 'Save configuration', I get an error as expected. However, if I then click "Reset to defaults", I continue to get the same error until I enter a valid input. Is there a way to skip validation when resetting to default values? I mean, at that point I really don't care what is entered in the form so why validate it? Thanks

adityamenon’s picture

This was a spectacular time saver. I simply interchanged the textbox with checkboxes and it 'just worked'. Drupal can be lovely sometimes!

pesoman’s picture

Hello I am new in module development how to specify the type to "select" (drop-down-list) ?

pikku-h’s picture

mccrodp’s picture

I couldn't get this working until I removed the 'type' index from items. i.e. I removed the line 'type' => MENU_NORMAL_ITEM,
I looked at the Jquery Update module's function jquery_update_menu and noticed it did not declare a type index.

cristiroma’s picture

I think there's a convention I saw in most of the modules to define the form into a separate file called "" and also the name of the function ends in my_module_admin_form. Here's an example I use:

 * Implements hook_menu().
function mymodule_menu() {
  $items = array();
  $items['admin/config/content/mymodule'] = array(
    'title' => 'Module settings',
    'description' => 'Configure my module',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('mymodule_admin_form'),
    'access arguments' => array('administer my module'),
    'position' => 'left',
    'weight' => -15,
    'file' => '',
  return $items;

So we can have three functions:

  1. my_module_admin_form
  2. my_module_admin_validate
  3. my_module_admin_submit

By adding 'file' parameter you separate this code into another file. Also you don't need to declare the file into files[] section of .info. It's loaded automatically.

Mingsong’s picture

Somehow, if there is an underscore in the path of a section of configuration page it won't work. For example:
The section menu item path with underscore:
And the setting page path:

Above menu item won't appear in the '/admin/config' page as expected.
Change the path to:

Then works. Your module setting section shows in the admin configuration page.

I test it on version 7.43.