How To Set Up Custom Field Type Using Drupal 7 Fields API

I recently worked on a project where we were using select lists in a node.  The problem we ran into was that the standard "list" field type provided by "fields" wasn't configurable enough for our scenario.  So I set out to create a custom field type and learned a lot on the way.

Requirements

This custom field needed to render as a select list but treat each entry individually.  The goal was to be able to set access restrictions for each entry so you could tailor who could or could not select certain options or under what circumstances an option could be selected based on the current value.

Implementation

If you are looking to create a custom field using the Fields API, I suggest you use the Examples Module as one of your primary reference points.  It contains much of what you will need to create an entirely custom field.  This post is not intended to be a start-to-finish tutorial on how to create a custom field.  My intent is to provide some explainations and examples which I did not find and would have found very useful.  I am also making the assumption that you have already created your custom module (.info and .module files).

Define Your Field

Setting up your field as a select-able option from the Manage Fields page of a content type requires three steps:

1. Define your field's schema by implementing hook_field_schema() in your .install file.  My field type only has one column for the "option" as the other colums - for role or workflow settings - are added by sub-modules.

  1. function my_module_field_schema($field) {
  2.   $schema = array();
  3.   $schema['columns']['option'] = array(
  4.       'type' => 'varchar',
  5.       'length' => 25,
  6.       'not null' => FALSE
  7.   );
  8.   return $schema;
  9. }

2. Define your field to the Fields API by implemeiting hook_field_info() in your .module file.  When implemented, the name will be "My Field".  All instances of this field will have a default max length of 255.  You can put any default settings here if you would like or remove this line entirely if it does not apply to your implementation.

  1. function my_module_field_info() {
  2.   return array(
  3.     'my_field_name' => array(
  4.       'label' => t('My Field'),
  5.       'description' => t('Description of My Field.'),
  6.       'settings' => array('max_length' => 255),
  7.       'instance_settings' => array(
  8.         'text_processing' => 0,
  9.       ),
  10.       'default_widget' => 'options_select',
  11.       'default_formatter' => 'states_field_options',
  12.     ),
  13.   );
  14. }

3. Define the widget you will use to render your field.

  • If your field uses a custom widget, you can define that by implementing hook_field_widget_info() and hook_field_widget_form().  The Examples Module provides informaiton on these functions.
  • If your field will use an existing widget, you include that widget as an option for your field by implmenting hook_field_widget_info_alter() in your .module file.  I want my field to render using only a select list (widget options_select) so here is how my implementation would look:
  1. function my_module_field_widget_info_alter(&$info) {
  2.   $widgets = array(
  3.     'options_select' => array('my_field_type'),
  4.   );
  5.   foreach ($widgets as $widget => $field_types) {
  6.     $info[$widget]['field types'] = array_merge($info[$widget]['field types'], $field_types);
  7.   }
  8. }

Field Settings

Now you have a field ready to be selected from the Manage Fields page.  You now need to define the settings for your field.  How will the form look? how will it validate? how will it return the list of options?

You first need to define the form for your field settings.  Fields support both field settings and instance settings.  In case you aren't familiar, field settings apply to all places where that field is used and Instance settings apply only to that instantiation of that field.  Some field types support only field settings, others support only instance settings and some support both.  The Drupal API provides a warning which should be heeded concerning field settings.

Create your form using hook_field_settings_form() and hook_field_instance_settings_form() as appropriate.  Both of these function take field and instance as variables.  Build the form using standard Drupal conventions.  It should be noted that the Fields API expects the form elements to be ordered in the same as your schema definition.  The names of form elements can differ, but the order is important for the Field SQL Storage module.

You can do validation of your form elements in the way that best suits you.  Because of the way that my field was created and implemented, I chose to do element validation (using the #element_validate property of a form element) rather than use hook_field_validate().  I did so because I wanted to validate the settings for the field and not the implementation and hook_field_validate is called any time the field is rendered - both for field settings and node creation/edit.  This also allowed me to keep my validate handler cleaner and more effecient.

  1. function my_module_element_validate($element, &$form_state) {
  2.   switch ($element['#type']) {
  3.     case 'textfield':
  4.       if ($element['#value'] == '') {
  5.         form_error($element, t('Case Name may not be blank.'));
  6.       }
  7.       break;
  8.     case 'checkboxes':
  9.       if (empty($element['#value'])) {
  10.         form_error($element, t('You must give at least one role access to this state.'));
  11.       }
  12.       break;
  13.   }
  14. }

As the Fields API handles the submission and storage of your form elements, you do not need to implement a custom submit handler unless it is to alter the data prior to saving.

If your field will provide multiple options or need to render a list of any sort, you need to implement hook_options_list() to return the array of options back to the Fields API to populate your widget.

  1. function my_module_options_list($field, $instance, $entity_type, $entity) {
  2.   return array('option_1', 'option_2');
  3. }

Create your Display

The last item necessary to create your field is to define how it will render.  You can create multiple display settings if desired - only one was necessary for my implementation.  Drupal will use the name of your field as the label so you do not need to worry about that, only return the appropriate value for the field. 

1. Define your field display (formatter) using hook_field_formatter_info().

  1. function states_field_field_formatter_info() {
  2.   return array(
  3.     'my_field_default' => array(
  4.       'label' => t('Default'),
  5.       'field types' => array('my_field_name'),
  6.     ),
  7.   );
  8. }

2. Return data for display when rendered using hook_field_formatter_view().

  1. function my_module_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
  2.   $element = array();
  3.   //Currently we only have one display option, use switch
  4.   //to prepare for future options.
  5.   switch ($display['type']) {
  6.     case 'my_field_default':
  7.       foreach ($items as $delta => $item) {
  8.         if (isset($options[$item['option']]['option'])) {
  9.           $output = field_filter_xss($options[$item['option']]['option']);
  10.         }
  11.         else {
  12.           $output = "Not Supplied";
  13.         }
  14.         $element[$delta] = array('#markup' => $output);
  15.       }
  16.       break;
  17.     default:
  18.   }
  19.   return $element;
  20. }

 

Hope you find this helpful, happy coding!