Drupal 7 Field API: Creating Custom Field Types, Widgets and Formatters

This page is archived

We're keeping this page up as a courtesy to folks who may need to refer to old instructions. We don't plan to update this page.

Alternate resources

This series is for anyone that wants to learn more about how Drupal 7's Field API works from a module developer's perspective. Ever wonder how the text or image fields in Drupal work? Or how to create your own new custom field type and encapsulate the functionality provided by your module so that it can be mixed and matched with the rest of Drupal's fields by site administrators? This series will cover everything necessary to implement your first custom field type in Drupal 7.

Fields are the building blocks of Drupal's powerful content modeling system and the Field API allows for the development of custom field types to suit almost any data display and collection needs. When combined with the Entity API, which allows for the attachment of fields to entity bundles (commonly known as content types), Drupal becomes infinitly flexible without requiring writing any code.

There's already a pretty large ecosystem of Drupal 7 field types implemented by either Drupal core or other contributed modules, but what happens when the one you need doesn't exist? As a module developer knowing how to implement the Drupal 7 Field API and encapsulate your custom logic and functionality into a field can often times allow you to create modules that are far more reusable since they don't assume a specific information architecture.

We'll start out this series with a discussion of the pieces that make up the Drupal 7 Field API and some of the terminology like types, formatters, and widgets that you'll encounter when learning the Field API. We'll also talk about how fields are related to entities and the sometimes confusing concepts of field types and field instances. Having a firm grasp on the underlying concepts makes it easier to see how all the moving parts fit together, allowing you to have a better idea of where or when you might want to create a new field, or extend an existing one.

Then we'll take a more in-depth look at defining a new custom field type and the hooks we need to implement in order to teach Drupal about the data our field is going to store, and how and where to store it. In addition to figuring out how to store the data we'll also look at validating the data before saving it to the database to confirm that it meets our requirements.

After figuring out how to store the data we'll then look at using field widgets to allow users to of our site to input data for our fields via an HTML form element. Then we'll see how field formatters take that collected data and prepare it to be displayed to our end users. Both widgets and formatters allow for module developers to define additional configuration settings for administrators creating field instances. So we'll look at how to add settings in both of those scenarios.

After watching this series you should be able to:

  • Explain the different between field types, widgets, and formatters and what each one does.
  • Add a new custom field type.
  • Create a custom field widget and use it with your own field or an existing field type.
  • Define a custom field formatter and use it with your own or existing field type.
  • Peform additional operations on the data stored in a field when it's being loaded.

Prerequisites

This series requires an understanding of PHP and basic Drupal 7 module development. For a refresher, or if you get stuck, check out our Drupal 7 Module Development series.

Tutorials in this course
More information

Fields are the building blocks of Drupal's powerful content modeling system. The field API allows for the development of custom field types to suit almost any data display and collection needs. Developers can create custom field types that can be bundled together and attached to various pieces of content. Fields allow a Drupal Site Administrator to create an information architecture that matches the needs of each individual site.

This series will provide you with all the information you need to be able to define a custom field in your own module. After completing all the lessons in this series, you should have a firm grasp of the Drupal 7 field API and the tools and knowledge you need in order to define your own custom field types.

Prerequisites

This series requires an understanding of PHP and basic Drupal 7 module development. For a refresher, or if you get stuck, check out our Drupal 7 Module Development series.

Additional resources

Drupal 7 Module Development series (Drupalize.Me).

More information

Before diving into the code it's important to understand some of the building blocks that make up the Field API. There's a lot of different terminology in the Field API and it helps to understand what each of the terms mean. As well as understanding the relationship between the Fields and Entities in Drupal 7. Knowing these things will give you a strong foundation on which to start exploring the Drupal 7 Field API.

Terms & concepts covered in this video:

  • What is a field? what is an instance?
  • How do fields relate to entities?
  • Field types
  • Field storage
  • Field widgets
  • Field formatters

For more information about these terms see the Drupal.org handbook page: https://drupal.org/node/443540

The Field API in Drupal 7 is actually made up of a bunch of different APIs. In this series we'll be focusing on the Field types API which is used to allow modules to define new field types, widgets, and display formatters. The Field types API is the most commonly used by custom modules.
 
The Field API consists of a set of hooks that you can implement to define your own custom field types with their own unique data collection widgets, storage schema, and behaviors. And sets of functions that can be called to do things like retrieve information about defined fields, pragmatically attach fields to an entity type, and interact in other ways with fields defined by core or other modules.
 
In addition to the Field types API there is also:
 
  • Field CRUD API - creates field instances and bundles, e.g.) what you see on the manage fields page.
  • Field attach API - connects entities and fields, uses info from Field Info API to retrieve defined fields and do things like display their widget on the appropriate entity form when someone tries to edit an entity.
  • Field info API - retrieve information about defined fields and instances.
  • Field storage API - pluggable back-end storage for fields. Defaults to SQL backend provided by core.
  • Field language API - provides native multilingual support for fields.
More information

There's quite a bit of documentation and other resources already available to help you better understand the Drupal 7 Field API. Lets take a look at what's already available on Drupal.org, in the Examples project, and in the Drupal 7 core code that will serve as good reference material. We'll be referring back to these resources in later lessons, and they'll serve as a great place to look up additional information or to continue your learning via other examples.

Resources covered in this video:

We've also got some additional resources here on Drupalize.Me that will serve as a good refresher for how/where fields are used in Drupal:

Additional resources

Handbook for Field API

field.api.php

Field API documentation available on api.drupal.org

Examples for Developers project

More information

Before we can start building our custom field we need a vanilla Drupal site to work with and a skeleton module. This lesson will ensure you've got Drupal 7 up and running and walk through creation of a basic .info file and .module file for the module we'll be building. If you're already familiar with Drupal module development this lesson can likely be skipped and you can simply download the attached starter files, add them to an existing Drupal site, and continue on with the next lesson.

Grab a fresh copy of Drupal 7, and install it. If you need a refresher on installing Drupal checkout this series.

You'll also want to download and install the devel module as we'll make use of some of the debugging functions in provides (namely dsm()) in later lessons in this series.

Alternatly, you can grab the .zip file under the companion files listed on this page which contains Drupal 7, and a database dump you can import to get started.

More information

The first step to defining a custom field is telling Drupal that our module provides a field. This is done by implementing hook_field_info(), hook_field_formatter_info(), and hook_field_widget_info(). The combination of which provides some basic information about our field including a label, description, default settings, and basic information about how the field will be formatted and what widgets can be used for data collection.

In this lesson we'll implement the basics for the following hooks:

Doing so will allow us to enable our module and see our new field type appear in the list of available fields to add to a content type. The field won't do much beyond that yet, but it's a good start towards telling Drupal about our custom RGB field type.

If you want to just follow along and look at the already written code you can grab a copy in the companion files section of this page and use that to follow along.

More information

Before we can actually get our field to store data for us we need to define what the data that we're going to store looks like. The Field API does this with hook_field_schema(), which uses a very similar syntax to what is used by the hook_schema() that modules can use to define database tables. In this particular case though we're only defining what the column, or columns, that store our specific data will be and allowing the Field Storage API to decide what the structure of the created table, or tables, should be. This allows our field to remain mostly storage system agnostic and frees us from having to worry about things like how the stored field data is connected back to the entity that it belongs to, or how to format our table for proper handling of revision data or translations.

Example hook_field_schema() implemenatation.


/**
 * Implements hook_field_schema().
 */
function rgb_field_schema($field) {
  $columns = array(
    'rgb' => array(
      'type' => 'varchar',
      'length' => 6,
      'not null' => FALSE,
    ),
    'label' => array(
      'type' => 'varchar',
      'length' => 128,
      'not null' => FALSE,
    ),
  );

  $indexes = array(
    'rgb' => array('rgb'),
  );

  return array(
    'columns' => $columns,
    'indexes' => $indexes,
  );
}

Additional resources

More information

The term widget refers to the form element, or elements, that are presented to the user when they are entering data for a field. For example, the file upload field on the Article content type is the widget for the image field attached to that content type. When a field instance is attached to a bundle and an admin is creating or editing an entity of that bundle type the Field Attach API calls out to each individual field and asks it for the widget it would like to use to collect data. Adding a widget for a custom field type is a combination of implementing hook_field_widget_info() and hook_field_widget_form().

Examples:


/**
 * Implements hook_field_widget_info().
 */
function rgb_field_widget_info() {
  return array(
    'rgb_textfield' => array(
      'label' => t('RGB Textfields'),
      'field types' => array('rgb_color'),
    ),
  );
}

/**
 * Implements hook_field_widget_form().
 */
function rgb_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) {

  if ($field['cardinality'] == 1) {
    $element['#type'] = 'fieldset';
  }

  $element['rgb'] = array(
    '#type' => 'textfield',
    '#field_prefix' => t('RGB: #'),
    '#size' => 6,
    '#default_value' => isset($items[$delta]['rgb']) ? $items[$delta]['rgb'] : '',
  );

  $element['label'] = array(
    '#type' => 'textfield',
    '#field_prefix' => t('Color name: '),
    '#default_value' => isset($items[$delta]['label']) ? $items[$delta]['label'] : '',
  );

  return $element;
}

Additional resources

More information

Before our field can save user provided data we need to use hook_field_validate() and hook_field_is_empty() to perform validation on field data. In certain context values like 0, FALSE, and NULL can all be a valid value. In fact, even a blank space could be valid input for a field. As such, it's not possible for Drupal to know what constitutes an empty state for a field without a little extra help. The same is true for checking if the value of a field is valid.

Examples:


/**
 * Implementation of hook_field_is_empty().
 */
function rgb_field_is_empty($item, $field) {
  if (empty($item['rgb']) || empty($item['label'])) {
    return TRUE;
  }
  return FALSE;
}

/**
 * Implements hook_field_validate().
 */
function rgb_field_validate($entity_type, $entity, $field, $instance, $langcode, $items, &$errors) {
  foreach($items as $delta => $item) {
    if (!empty($item['rgb'])) {
      // Make sure it's 6 characters.
      if (drupal_strlen($item['rgb']) !== 6) {
        $errors[$field['field_name']][$langcode][$delta][] = array(
          'error' => 'rgb_length',
          'message' => t('%name: the hex color must be 6 characters.', array('%name' => $instance['label'])),
        );
      }
    }
  }
}

Additional resources

hook_field_validate()

hook_field_is_empty()

More information

In order to allow for maximum flexibility in our widget we can add widget settings that apply to each individual instance of our field. By implementing hook_field_widget_settings_form() and then refactoring some of our existing code we can make it possible for a site administrator to set a custom prefix value for the label field, which the Field API will store for as part of the field instance's settings and we can use it when creating our widget.

Implementations of hook_field_widget_settings_form() return a Form API array that represents the element or elements that you would like to add to the widget settings form. Values are automatically serialized and saved as part of the field's instance configuration and can be accessed by the passed in $instance array's $instance['widget']['settings'] key.

Example:


/**
 * Implements hook_field_widget_settings_form().
 */
function rgb_field_widget_settings_form($field, $instance) {
  $element = array(
    'rgb_label_text' => array(
      '#type' => 'textfield',
      '#title' => t('Alternate label text'),
      '#description' => t('If an alternate label text is provided it will be used in place of the default "Color" title for the label field.'),
      '#default_value' => isset($instance['widget']['settings']['rgb_label_text']) ? $instance['widget']['settings']['rgb_label_text'] : '',
    ),
  );

  return $element;
}

Additional resources

More information

Displaying that data that was collected and saved for our a field requires creating a field formatter. Formatters consist of an implementation of hook_field_formatter_info() and hook_field_formatter_view(). The former provides meta-data about the formatter for the Field API and the latter does the heavy lifting of determining what the output is actually going to look like.

A module can define more than one field formatter.

Implementations of hook_field_formatter_view() return a renderable array representing the content you would like to display to the end user. Generally this is an escaped version of content provided by a site administrator with some additional HTML formatting applied.

Depending on the values being output you would likely want to also use a theme function for your field formatter by implementing hook_theme() and providing either a theme() function that can be overriden or a template file. We're not going to cover that in this lesson since the focus here is on the technical requirements for impelementing a field formatter. Howerver, I would say that it's best practcie to always output any HTML with a theme function. You can find out more about creating themeable output by watching these videos from our library: http://drupalize.me/videos/integrating-theme-system and http://drupalize.me/videos/using-drupal-render-api

Example:


/**
 * Implements hook_field_formatter_info().
 */
function rgb_field_formatter_info() {
  return array(
    'rgb_raw' => array(
      'label' => t('Raw color value'),
      'field types' => array('rgb_color'),
    ),
    'rgb_box' => array(
      'label' => t('Color block with label'),
      'field types' => array('rgb_color'),
    ),
  );
}

/**
 * Implements hook_field_formatter_view().
 */
function rgb_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, &$items, $display) {
  $element = array();

  switch ($display['type']) {
    case 'rgb_raw':
      foreach ($items as $key => $value) {
        $element[$key] = array(
          '#type' => 'markup',
          '#markup' => t('#@hex', array('@hex' => $value['rgb'])),
        );
      }
      break;

    case 'rgb_box':
      foreach ($items as $key => $value) {
        $element[$key] = array(
          '#type' => 'markup',
          '#markup' => '
' . check_plain($value['label']) . '
', ); } break; } return $element; }

Additional resources

More information

Sometimes display formatters need to allow for administrators to configure additional settings. For example choosing which image style to use when displaying an image field. The Field API allows for formatter settings and we can add them by implementing hook_field_formatter_settings_summary() and hook_field_formatter_settings_form(). This lesson shows how to add simple width and height settings for the rgb_box display formatter that will allow an admin to modify the dimensions of the block that is displayed. Then uses those entered values in the implementation of hook_field_formatter_view() added in the previous lesson to set the CSS width and height of the HTML element being displayed. Allowing site administrators a greater amount of control over what the content looks like without having to write any code. Which, also makes are module more flexible, and more useful in a larger variety of scenarios.

Field formatter settings are access via the Manage Display tab for our Article content type. Any field which provides additional settings will display a gear icon along on the far right that once clicked will reveal the settings form. Field formatter settings are per instance settings.

In addition to providing a settings form we also need to provide a simple text sumary of the settings that can be displayed on the Manage Display tab. This summary is displayed next to our field prior to someone clicking the gear icon that reveals the settings form. This gives the administrator a quick overview of the current configuration for all fields formatters. This is done with hook_field_formatter_settings_summary(), which despite not being documented as such is required in order to provide field display formatter settings in Drupal 7.

Example:


/**
 * Implements hook_field_formatter_settings_summary().
 */
function rgb_field_formatter_settings_summary($field, $instance, $view_mode) {
  $display = $instance['display'][$view_mode];
  $settings = $instance['display'][$view_mode]['settings'];

  if ($display['type'] == 'rgb_box') {
    $output = t('Box size: @widthx@height', array('@width' => $settings['width'], '@height' => $settings['height']));
    return $output;
  }
}

/**
 * Implements hook_field_formatter_settings_form().
 */
function rgb_field_formatter_settings_form($field, $instance, $view_mode, $form, &$form_state) {
  $display = $instance['display'][$view_mode];
  $settings = $display['settings'];

  $element = array();
  if ($display['type'] == 'rgb_box') {
      $element['width'] = array(
        '#type' => 'textfield',
        '#title' => t('Box width'),
        '#default_value' => $settings['width'],
      );
      $element['height'] = array(
        '#type' => 'textfield',
        '#title' => t('Box height'),
        '#default_value' => $settings['height'],
      );
  }
  
  return $element;
}

Additional resources

hook_field_formatter_settings_summary()

hook_field_formatter_settings_form()

More information

Sometimes we need to make use of the collected and stored field data in order to calculate additional values that can be used when displaying a field. The Amazon ASIN field for example can query the Amazon API and get additional information about a product like a thumbnail to display alongside the ASIN value. Using hook_field_load() we can perform additional operations on the stored field values at the time the Field API loads, or requests, the value of our field and present that calculated data long with the stored data.

Should the data be loaded during "view" or "load" operations? In my mind that depends on what it's needed for. A good question to ask might be, "if someone was accessing the content of this field as JSON would they want this data included?". If the answer is yes, you probably want to use hook_field_load() to perform addition load operations.

When dealing with implementations of hook_field_load() the most interesting paramater is probably the $items array. An multi-dimentional array that contains a record for each value value for this particular field for the entity being viewed. Because $items can contain one or more values you'll need to loop over the values within $items and update them accordingly.

Implementations of hook_field_load() don't need to return any value. Instead they should update the $items array which is passed in by reference.

In this example we'll be querying the Google Search API, which returns JSON data. The search API is accessible at URLs like the following: https://ajax.googleapis.com/ajax/services/search/web?v=1.0&q=663399. The most important part being the &q=663399, which we'll replace with our specific search term.

Values that are added to the $items array will be available to implementations of hook_field_formatter_view() in the $items parameter passed to those functions. From there, you can make use of any added data when displaying the field for end users. Because the data is added during the load operation for the entity that the field is attached to it's also available anytime you're making use of the $entity object.

Example:


/**
 * Implements hook_field_load().
 */
function rgb_field_load($entity_type, $entities, $field, $instances, $langcode, &$items, $age) {
  dsm('rgb_field_load');
  foreach ($items as $entity_id => $field_values) {
    foreach ($field_values as $delta => $value) {
      $url = 'https://ajax.googleapis.com/ajax/services/search/web?v=1.0&q=' . $value['rgb'];
      $response = drupal_http_request($url);
      if ($response->code == 200) {
        $data = drupal_json_decode($response->data);
        $links = array();
        foreach ($data['responseData']['results'] as $result) {
          $links[] = l($result['titleNoFormatting'], $result['url']);
        }

        $items[$entity_id][$delta]['google_links'] = $links;
      }
    }
  }
}

Additional resources

This course appears in the following guides:
Categories
Module Development, Backend and Infrastructure
Drupal 7, 8, 9, and 10