Create a custom field type

Last updated on
5 April 2017

Field types define the properties and behaviours of fields. Field types 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 type in Drupal 8 you need a class with the FieldType annotation.

The location of the field type class should be placed inside MODULE_NAME/src/Plugin/Field/FieldType
/modules/foo/src/Plugin/Field/FieldType/BazItem.php

The namespace of that class should be \Drupal\MODULE_NAME\Plugin\Field\FieldType
\Drupal\foo\Plugin\Field\FieldType\BazItem

The annotation above the class, within the doc comment, should include a unique id, a label, and the default formatter. The default formatter will be the ID used in the annotation of the field formatter class.

/**
 * Provides a field type of baz.
 * 
 * @FieldType(
 *   id = "baz",
 *   label = @Translation("Baz field"),
 *   default_formatter = "baz_formatter",
 *   default_widget = "baz_widget",
 * )
 */

The class needs to implement the FieldItemInterface interface. And should extend the FieldItemBase class for common implementation of the interface.

class BazItem extends FieldItemBase implements FieldItemInterface {

}

FieldItemInterface::schema() should be overridden, in order to tell the system how to store the value(s) for the field

/**
 * {@inheritdoc}
 */
public static function schema(FieldStorageDefinitionInterface $field_definition) {
  return array(
    // columns contains the values that the field will store
    'columns' => array(
      // List the values that the field will save. This
      // field will only save a single value, 'value'
      'value' => array(
        'type' => 'text',
        'size' => 'tiny',
        'not null' => FALSE,
      ),
    ),
  );
}

The method FieldItemInterface::propertyDefinitions() tells the system more details about your properties of the field

/**
 * {@inheritdoc}
 */
public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) {
  $properties['value'] = DataDefinition::create('string');

  return $properties;
}

The method Map::isEmpty (an ancestor of FieldItemBase) should be overridden, telling the system how to determine when the field is empty.

/**
 * {@inheritdoc}
 */
public function isEmpty() {
  $value = $this->get('value')->getValue();
  return $value === NULL || $value === '';
}

Field Settings

Field settings allow users to customize the field according to their needs. If the field has any field settings, three steps need to be taken:

  1. Override FieldItemBase::defaultFieldSettings() 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 FieldItemBase::defaultFieldSettings()

/**
 * {@inheritdoc}
 */
public static function defaultFieldSettings() {
  return [
    // Declare a single setting, 'size', with a default
    // value of 'large'
    'size' => 'large',
  ] + parent::defaultFieldSettings();
}

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 defaultFieldSettings():

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

field.field_settings.[FIELD ID]:
  type: mapping
  label: 'FIELDNAME settings'
  mapping:
    size:
      type: string
      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 FieldItemBase::fieldSettingsForm()

/**
 * {@inheritdoc}
 */
public function fieldSettingsForm(array $form, FormStateInterface $form_state) {

  // The key of the element should be the setting name
  $element['size'] = [
    '#title' => $this->t('Size'),
    '#type' => 'select',
    '#options' => [
      'small' => $this->t('Small'),
      'medium' => $this->t('Medium'),
      'large' => $this->t('Large'),
    ],
    '#default_value' => $this->getSetting('size'),
  ];

  return $element;
}

Real Example

The TextWidget from the field_example module under the examples project:

namespace Drupal\field_example\Plugin\Field\FieldType;

use Drupal\Core\Field\FieldItemBase;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\TypedData\DataDefinition;

/**
 * Plugin implementation of the 'field_example_rgb' field type.
 *
 * @FieldType(
 *   id = "field_example_rgb",
 *   label = @Translation("Example Color RGB"),
 *   module = "field_example",
 *   description = @Translation("Demonstrates a field composed of an RGB color."),
 *   default_widget = "field_example_text",
 *   default_formatter = "field_example_simple_text"
 * )
 */
class RgbItem extends FieldItemBase {

  /**
   * {@inheritdoc}
   */
  public static function schema(FieldStorageDefinitionInterface $field_definition) {
    return array(
      'columns' => array(
        'value' => array(
          'type' => 'text',
          'size' => 'tiny',
          'not null' => FALSE,
        ),
      ),
    );
  }

  /**
   * {@inheritdoc}
   */
  public function isEmpty() {
    $value = $this->get('value')->getValue();
    return $value === NULL || $value === '';
  }

  /**
   * {@inheritdoc}
   */
  public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) {
    $properties['value'] = DataDefinition::create('string')
      ->setLabel(t('Hex value'));

    return $properties;
  }

}