Create a custom field widget

Last updated on
24 January 2018

This documentation is incomplete. Add more information.

Field widgets are used to render the field inside forms. Field widgets are defined as plugins, so it's a good idea to familiarize yourself with the Plugin API before diving in to writing a new field type.

To create a field widget in Drupal 8 you need a class with the FieldWidget annotation.

The location of the field widget class should be MODULE_NAME/src/Plugin/Field/FieldWidget. For example, /modules/foo/src/Plugin/Field/FieldWidget/BarWidget.php.

The namespace of that class should be \Drupal\MODULE_NAME\Plugin\Field\FieldWidget. For example, \Drupal\foo\Plugin\Field\FieldWidget.

The annotation above the class should include a unique id, a label and an array of ids of field types that this widget can handle.

/**
 * A widget bar.
 *
 * @FieldWidget(
 *   id = "bar",
 *   label = @Translation("Bar widget"),
 *   field_types = {
 *     "baz",
 *     "string"
 *   }
 * )
 */

The class needs to implement the WidgetInterface interface. And can extend the WidgetBase class for common implementation of the interface.

class BarWidget extends WidgetBase implements WidgetInterface {

  /**
   * {@inheritdoc}
   */
  public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
    $element = [];
    // Build the element render array.
    return $element;
  }

}

Widget Settings

If your formatter needs custom display settings, there are three steps required to achieve this:

  1. Override PluginSettingsBase::defaultSettings() in order to set the defaults
  2. Create the configuration schema for the settings you've created
  3. Create a form to allow users to change the settings

Step 1: Override PluginSettingsBase::defaultSettings() in order to set the defaults

/**
 * {@inheritdoc}
 */
 public static function defaultSettings() {
  return [
    // Create a default setting 'size', and
    // assign a default value of 60
    'size' => 60,
  ] + parent::defaultSettings();
}

Step 2: Create the configuration schema for the settings you've created

Configuration schema goes in the following file:

[MODULE ROOT]/config/schema/[MODULE_NAME].schema.yml

In this file, you will describe the data type(s) of the setting(s) you've created in defaultSettings():

Step 1 created a setting named 'size', that stores an integer value. The schema for this would look like the following:

field.widget.settings.[WIDGET ID]:
  type: mapping
  label: 'WIDGET NAME widget settings'
  mapping:
    size:
      type: integer
      label: 'Size'

Step 3 - Create a form to allow users to change the settings

The form to allow for users to change the values for the settings is created by overriding WidgetBase::settingsForm()

/**
 * {@inheritdoc}
 */
public function settingsForm(array $form, FormStateInterface $form_state) {
  $element['size'] = [
    '#type' => 'number',
    '#title' => t('Size of textfield'),
    '#default_value' => $this->getSetting('size'),
    '#required' => TRUE,
    '#min' => 1,
  ];

  return $element;
}

You can also list the selected settings in the summary for the widget as follows:

/**
 * {@inheritdoc}
 */
public function settingsSummary() {
  $summary = [];

  $summary[] = t('Textfield size: @size', array('@size' => $this->getSetting('size')));

  return $summary;
}

You can use the getSetting() method of the class to retrieve the setting, for use in the widget:

class BarWidget extends WidgetBase implements WidgetInterface {

  /**
   * {@inheritdoc}
   */
  public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
    $element['value'] = $element + [
      '#type' => 'textfield',
      '#default_value' => isset($items[$delta]->value) ? $items[$delta]->value : NULL,
      '#size' => $this->getSetting('size'),
    ];

    return $element;
  }

}

Example Widget

The TextWidget from the field_example module under the examples project:

namespace Drupal\field_example\Plugin\Field\FieldWidget;

use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\WidgetBase;
use Drupal\Core\Form\FormStateInterface;

/**
 * Plugin implementation of the 'field_example_text' widget.
 *
 * @FieldWidget(
 *   id = "field_example_text",
 *   module = "field_example",
 *   label = @Translation("RGB value as #ffffff"),
 *   field_types = {
 *     "field_example_rgb"
 *   }
 * )
 */
class TextWidget extends WidgetBase {

  /**
   * {@inheritdoc}
   */
  public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
    $value = isset($items[$delta]->value) ? $items[$delta]->value : '';
    $element += [
      '#type' => 'textfield',
      '#default_value' => $value,
      '#size' => 7,
      '#maxlength' => 7,
      '#element_validate' => [
        [static::class, 'validate'],
      ],
    ];
    return ['value' => $element];
  }

  /**
   * Validate the color text field.
   */
  public static function validate($element, FormStateInterface $form_state) {
    $value = $element['#value'];
    if (strlen($value) == 0) {
      $form_state->setValueForElement($element, '');
      return;
    }
    if (!preg_match('/^#([a-f0-9]{6})$/iD', strtolower($value))) {
      $form_state->setError($element, t("Color must be a 6-digit hexadecimal value, suitable for CSS."));
    }
  }

}