Last updated November 23, 2013. Created on August 23, 2007.
Edited by areke, Alan D., drupalshrek, chrisjlee. Log in to edit this page.

Drupal's Form API (FAPI) makes it possible to create custom types of form elements if the standard ones (such as textfield or checkbox) are not enough for your application. Here's how to declare and use them. You may find the Form Workflow Illustration helpful here. In this example we use $element instead of $form in places where only the element's portion of the form is available, just to make it more clear what you have to work with.

A simple functional example from the Drupal API can be found here (within the form_example submodule).

Hook Elements

You can start creating a custom element type by implementing in a module hook_element_info() (Drupal 7), or hook_elements() (Drupal 6) . It should return a simple associative array with the name of each custom element type as a key and an array of attributes describing the type as a value. Some of the attributes specify optional custom callbacks. Assuming you are doing this in a module called 'example', it might look like:

function example_elements() {
   return array('example_field' => array(
     '#input' => TRUE, 
     '#process' => array('example_field_process'),
     '#element_validate' => array('example_field_validate'),
     '#example_attribute' => 'Something extra',
  ));
}

#input tells FAPI that this is an element that will carry a value, even if it is a hidden value. #process, #element_validate, etc. are arrays of callback function names. Each of those callbacks will get $element and &$form_values as arguments. Some of the most common attributes include:

#process
#validate
#element_validate

#example_attribute is not an attribute specified by FAPI; it is a custom attribute that has been added to the hook. To be sure there are no namespace clashes, it is a good idea to prefix custom attributes with the module name. The standard FAPI attributes will be explained in more detail later.

It is not necessary to use hook_element_info() to use these FAPI features, as you can add things like #process to any form element as you create it. Using hook_element_info() to declare an element will create a reusable element (proto)type. Individual form elements of this type will inherit by default whatever values and processing functions you specify in the hook.

Element Themes

If you use hook_element_info() to define an element type, there is an implicit assumption that there will be a theme function with the same name as the element type used to render HTML of its instances. In this example, the elements will be passed to a theme function called theme_example_field(), if it exists. You don't need to add #theme to hook_elements() (note: in Drupal 7, you do need to add #theme to hook_element_info()), it is assumed. If a theme function with the expected name does not exist and there is no other #theme set in the element, nothing will appear in the form.

Your element theme might look like the following. You can alter the element before rendering it, if desired.

function theme_example_field($element) {
  // Put your own rendering logic.
  $output .= '
'. t('My field') .'
'; $output .= '
'. $element['#some_value'] .'
'; // Or even render child elements standard way // WARNING: Make sure that sub_element is not and doesn't contain the element of your type, as you'll get a recursion. $output .= '
'. drupal_render($element['sub_element']) .'
'; return $output; }

Starting with Drupal 6.x, all themes must be registered in hook_theme(), so add the following hook_theme() function (or add this code to an existing hook_theme()). Element themes always get only one argument, the element ($element).

function example_theme() {
  return array(
    'example_field' => array(
       'arguments' => array('element' => NULL)
    ),
  );
}

Add Form Element to Form

Now that the custom element type is registered, add some elements of that type to a form, perhaps using something like hook_form_alter(). Do this the same way you add standard elements to a form. For example, you could do something like:

function example_form_alter(&$form, &$form_state, $form_id) {
  if (isset($form['type']) && isset($form['#node']) && $form['type']['#value'] .'_node_form' == $form_id) {
    $form['my_field'] = array(
      '#type' => 'example_field',
      '#title' => t('My example Field'),
      '#default_value' => t('Good News!'),
     );
   }
}

Although you only added #title and #default_value to your form element, it will also pick up values from hook_element_info(), so in this example the complete element will become:

$form['my_field'] = array(
  '#type' => 'example_field',
  '#title' => t('My example Field'),
  '#default_value' => t('Good News!'),
  '#process' => array('example_process'),
  '#element_validate' => array('example_validate'),
  '#example_attribute' => 'Something extra',
 );

Anything set in your implementation of the element will override the basic settings in hook_element_info(). For example, if you set #example_attribute to 'Nothing much' in hook_form_alter(), that would override the value set in hook_element_info() of 'Something extra'. You can use this feature to set sane default values for your element and to be sure those values are not empty, while still being able to override them later.

Value callback or form_type_hook_value()

Normally FAPI assigns #value either based on what posted when the form is executed using drupal_get_form() or drupal_execute(), or if there is nothing posted, then it copies #default_value.

The first opportunity after hook_form_alter() to change values is in form_type_hook_value(). This is another implied callback for any element defined in hook_element_info(). No attribute is specified for this callback in hook_element_info(); it is implemented simply by creating a function with a name like form_type_example_field_value, ('form_type_'. $element_name .'_value') as follows:

function form_type_example_field_value($element, $edit = FALSE) {
  if (func_num_args() == 1) {
     return $element['#default_value'];
  }
  return $edit;
}

The function is called in two different situations with different arguments. When there is no post, you only get one argument, $element, which is an array representing your element. When there is a post, $edit is whatever has been posted as a value for that element. Given that it's not possible to post FALSE from a form, it seems that using FALSE as a default value and branching on it is pretty safe. However, you can assign FALSE if you drupal_execute your form so it's even better to use func_num_args here, as in the example code above.

When the form is unposted the above example will set the value to the default_value of the field (the usual behavior). If $edit has a value, you could make changes to it before it is set. Anything you return here will be used as the value for the element.

If you just want #value set to #default_value, you don't need this hook at all, since that will happen anyway. This function is mainly useful if you need to do something non-standard with the value, like transpose an array into an option list.

Some of the core FAPI elements that use hook_value are select elements, which run drupal_map_assoc() over the posted $edit value, and password_confirm, which expands the default value into two fields.

There is another way to set the value for the field: by adding '#value_callback' to hook_element() or to your form element, and providing a custom function that sets the value. That callback will get the same arguments as above, but provides a more flexible way to set a value. You can use that to set a value on a nested sub-element like those CCK uses, to use the same value callback for several different elements, or to add a custom value callback to a standard FAPI element type.

Process Callback

The next opportunity in the form processing workflow to alter form values is in whatever callbacks are declared in the #process array of the element. These callbacks could be set by hook_element, or added to the element when the form is created, or both. The callbacks are in an array, and are processed in whatever order they appear in the element, so you can pass an element through several processes, and control the order that this will happen by appending a new callback to the beginning or end of the array to have it process the element first or last.

The #process callback gets four arguments, $element, $edit, $form_state and $complete_form, and expects to have $element returned. This is the last place you can override the default value. By this point in the processing, #default_value has been moved to #value in the element, so to change the value in here you need to alter $element['#value'], not $element['#default_value'].

function example_field_process($element, $edit, &$form_state, $complete_form) {
  $element['#value'] = t('My new value');
  return $element;
}

Several core FAPI element types use #process -- checkboxes use #process to expand one checkbox into several actual HTML input fields, date types use #process to expand a date into a month, day, and year input fields, and password uses #process to add a second password input field to the form.

The combination of $form_state, hook_value, #value_callback, and the ability to have multiple #process callbacks means it's possible to manipulate the element in many different ways between the time you create the form element and the time you set the element's value.

Note that $edit should not be passed here as a reference, as it may be NULL. The parameter, $complete_form, is a copy of the entire form and it is not passed by reference.

Afterbuild Callback

After the element passes through #process and its value is set, it will be passed to any #after_build callbacks. Just as with #process, there is an array of #after_build callbacks, so several can be set, and each will get $element and $form_state. At this point in the processing, you can expect #value to be properly and finally set, so this is an opportunity to make changes to the element that can only be made once #value will not change but before the form is submitted or rendered.

Validation Callbacks

There are two kinds of validation callbacks, #validate and #element_validate. The first does validation on the entire form, the second on only a single element.

Form-level validation happens before element validation, and receives as arguments $form and $form_state. Element validation receives only the element's portion of the form, along with the element's portion of $form_state.

In the above example, the element validation callback might look like:

function example_field_validate($element, &$form_state) {
  if ($element['#value'] <= 10) {
    form_error($element, t('The number must be more than 10.'));
    form_set_value($element, 0, $form_state);
  }
  return $element;
}

There are several core FAPI elements that use #element_validate. They include password_confirm, which checks that its two nested password fields match and unsets the extra fields once validation is done, and date, which checks that the date values create a valid date.

Rendering Callbacks

There are two callbacks that come into play after the form has been created and is ready to be presented to the user, or when validation fails and the form is re-drawn. They are #pre_render and #post_render. These callbacks allow you to make changes to the form presentation. The core FAPI elements don't use these callbacks, but they are available for use by custom or contributed modules. #pre_render will get $element as argument. #post_render gets $content and $element as arguments, where $content is the rendered element.

Submission Callbacks

The final callback is #submit. Like #process, it is an array which can contain several callbacks which will be called in the order they appear in the array. The submit callbacks get two arguments, $form, and $form_state. If #submit is set on an element of the form, instead of at the top level of the form, $form will contain only the element's part of the form.

As used by core, #submit seems to make the most sense and work the best if set at the form level and used to do things like update databases or take action based on the entire form results. None of the core FAPI elements use #submit at the element level.

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

Comments

pukku’s picture

Note that setting #input to TRUE is required to get an ID attribute on the element automatically, and may also be required to be true to have the #process handler run.

Alan D.’s picture

Process callback

The process callback should not pass $edit by reference, this may be NULL and it will generate an error. The array is generated from the form submission, and it is not the $form_state variable, rather it represents the $form['#post'].

<?php
function example_field_process($element, $edit) {
  $element['#value'] = t('My new value');
  return $element;
}
?>

From about Drupal 6 Beta 3, this has changed slightly. The parameter, $complete_form, is a copy of the entire form and it is not passed by reference.

<?php
function example_field_process($element, $edit, &$form_state, $complete_form) {
  // we can copy useful parameters from the complete form that
  // are not accessible in other hooks 
  $element['#build_id'] = $complete_form['#build_id'];
  return $element;
}
?>

Rendering Callbacks

The callback pre_render does not get passed the $form_state. This is either the element or whole form.

Eg: In form.inc

<?php
    foreach ($elements['#pre_render'] as $function) {
      if (function_exists($function)) {
        $elements = $function($elements);
      }
    }
?>

And the callback post_render is for filtering the outputted HTML from the rendered element / form.

It's arguments are custom_post_render($html, $form);

PS: Nice overview

munroe_richard’s picture

Most of the other callbacks in the structure returned by hook_element use arrays so that you can have more than one function process your custom form element during a particular phase of form processing. Because of this I thought that the #value_callback would be consistent with the other sorts of callbacks defined in the structure returned by hook_element.

Wrong.

The #value_callback is only a function name (string), not an array of function names (array(string, string, ...)). It took me a little while to sort that out, but it does work great.

Richard Munroe
Cottage Software Works, Inc.
Our Motto: Cottage Software Works!

skilip’s picture

The submit callbacks are actually only executed when either they are on form level, or on element level when #type is button or submit, and only if #executes_submit_callback is set to TRUE. Otherwise _form_builder_handle_input_element() won't pass the submit callbacks to $form_state['submit_handlers'].

beautifulmind’s picture

I've used hook_elements with #process directives and its working fine.
But when I add #pre_render it throws error like : Fatal error: Unsupported operand types in /home/myuser/web/myproject/includes/common.inc on line 2831

Is there any thing that I'm missing?

Regards.

Liam McDermott’s picture

You've probably worked this out by now, but you need to return $element; at the end of your pre-render function.

matslats’s picture

I'm making a currency widget. with two fields, call them dollars and cents.
I want the field[#value] to show a single float e.g. 1.23 to the rest of the form
However I'm starting to think this is not within the remit of hook_element
If I set #value not to be an array corresponding to the two sub-fields, everything breaks.

So where should I add $field['dollars']['#value'] to $field['cents']['#value'] so $field['#value'] = 1.23
?

Cubic8’s picture

For Drupal 7 the hook_element() has been renamed to hook_element_info()

Be careful with the theming. I found that theme_myfield() was not called by default and that I had to add a '#theme' => 'myfield' into my hook_element_info().

Also the theme function must be registered with 'render element'
eg.

function mymodule_theme($existing, $type, $theme, $path) {
  $data['myfield'] = array(
    'render element' => 'element',
    /* And NOT like this:
    'variables' => array('element' => NULL)
    */
  );
  return $data;
}
Alan D.’s picture

The value callback gets 3 arguments now.

function form_type_example_field_value($element, $edit, $form_state) {
  if ($edit === FALSE) {
     return $element['#default_value'];
  }
  return $edit;
}

It may be possible to modify $form_state here too, but I haven't tried. Change "$form_state" to "&$form_state" to try. This would require testing on form load, submit and AJAX to confirm, or by reading through a lot of api changes.

bsohal’s picture

Please assist, following is my sample code (I am in process of learning drupal 7): -


/**
 * Implement hook_menu().
 */
function alltaxny_menu() {
  $items = array();
  $items['prtitms'] = array(
    'title' => 'alltaxnys',
    'description' => 'My alltaxnys',
    'access arguments' => array('view own profile'),
    'page callback' => 'drupal_get_form',
    'page arguments' => array('alltaxny_txny_tree_form'),
    // 'weight' => -5,
    'type' => MENU_LOCAL_TASK,
  );
  return $items;
}

function alltaxny_elements_info() {
   return array('alltaxny_field' => array(
     '#input' => TRUE,
     '#process' => array('alltaxny_field_process'),
     '#element_validate' => array('alltaxny_field_validate'),
     '#theme' => array('alltaxny_field'),
     //'#alltaxny_attribute' => 'Something extra'
  ));
}

function theme_alltaxny_field($element) {
  // Put your own rendering logic.
  $output .= '
'. t('My alltaxny field') .'
'; //$output .= '
'. $element['#some_value'] .'
'; // Or even render child elements standard way // WARNING: Make sure that sub_element is not and doesn't contain the element of your type, as you'll get a recursion. // $output .= '
'. drupal_render($element['sub_element']); $output .= '
'; return $output; } function alltaxny_theme($existing, $type, $theme, $path) { return array( 'alltaxny_field' => array( 'render element' => 'element' // 'arguments' => array('element' => NULL) ), ); } function alltaxny_field_process($element, $edit, &$form_state, $complete_form) { $element['#value'] = t('My new value'); return $element; } function alltaxny_field_validate($element, &$form_state) { if ($element['#value'] <= 10) { form_error($element, t('The number must be more than 10.')); form_set_value($element, 0, $form_state); } return $element; } function form_type_alltaxny_field_value($element, $edit, $form_state) { if ($edit === FALSE) { return $element['#default_value']; } return $edit; } // BSOHAL, added function alltaxny_txny_tree_form($form, &$form_state) { // Get vocabulary // Things before this get printed to form ! $form['alltaxny_field'] = array( '#type' => 'alltaxny_field', '#title' => t('My alltaxny Field'), '#default_value' => t('Good News!'), ); // Things after this (here) also get printed to form ! return system_settings_form($form); }

Another problem in this code is that "prtitms" is not added to menu, however I can manually call the /prtitms to see the form !

studiotaffi’s picture

I'm in the same situation... and studying the examples...
let's see!

carlosalvet’s picture

I had problems with this, studying the example can't resolve de problem and I want to show my element and hooks used (it works), the code may has "writing errors" because was modified in this code area (just names and wide areas was reduced).

The module name is color and the element name is color_field, the use in your form api is as follow:

$form['myfield'] = array(
     '#type' => 'color_field',
     '#title' => t('My title'),
     //... all the attributes are used into function theme_color_field
  );

This code is the minimal configuration

/**
*  Implement hook_element_info()
*  Here is the info element, default values and element name for use with form api,
*  the element elementname is "color_field" and is used following for naming the
*  "theme_elementname()" hook
*/
function color_element_info() {
   return array('color_field' => array(
     '#type' => 'color_field',
     '#title' => '',
     '#id' => 'color-field',
     '#attributes' => array(),
     '#default_value' => '',
     '#description' => '',
     '#input' => TRUE,
     '#size' => 6,
     '#class' => array('1', '2', '3'),
     //'#process' => array('color_field_process'),
     //'#element_validate' => array('color_field_validate'),
     //'#sei_color_field_attribute' => 'Something extra',
     '#theme_wrappers' => array('color_field'),
  ));
}

/**
* implemente hook_element_theme() here is the conection for element with element theme (the render of info)
**/
function color_element_theme() {
  return array(
    'color_field' => array(
      'render element' => 'element',
    ),
  );
}

/**
* Implemente theme_elementname() 
* This is the more important for render your element,
* this has the element name (not hook name) and has de render element
**/
function theme_color_field($element) {
  // Put your own rendering logic.
  if(!is_array($element['element']['#attributes'])) {
    throw new Exception("el valor de #attributes en el campo 'color_field' debe de ser un arreglo válido", 1);
    
  }

  if(!is_array($element['element']['#class'])) {
    throw new Exception("el valor de #class en el campo 'color_field' debe de ser un arreglo válido", 1);
  }
  $output  .= '<label><?php echo  $element['element']['#title'] ?></label'
  $output .= '<input type="color" name="<?php echo $element['element']['#name'] ?>" id="<?php echo  $element['element']['#id'] ?> value="<?php echo $element['element']['#default_value'] ?>"/>';
 $output .=  '<div class="description"><?php echo  $element['element']['#description'] ?>'
  return $output;
}

/** this is for drupal 6, **/
/**
* Implement hook_theme
**/
function color_theme() {
  return array(
    'color_field' => array(
       'render element' => 'element' // 'arguments' => array('element' => NULL)
    ),
  );
}