Creating a custom field in Drupal 8

In this post I'll be discussing how one creates custom fields in Drupal 8. There's a variety of reasons why one might need to create a custom field. Perhaps there is functionality that is far too specific for a given requirement such that one needs to store and input data in a particular manner. The out of the box fields should suffice for all sorts of needs but there's always that rare situation where it is needed. So it's always handy to know HOW to create custom fields.

In Drupal 8, Fields as well as Blocks and many other things leverage the Plugin architecture. When you create a new field you're essentially extending a type of Plugin.

When you're creating a new Field there's typically 3 main goals:

  • You need to define how the data items are represented and stored in the database
  • You need to define how the form is rendered which is responsible for populating this data
  • You need to define how this data is rendered on the page

Let's tackle that first one. In order for Drupal to understand how it should represent your field in the database you need to define a class in your custom module in this path:

src/Plugin/Field/FieldType/CustomFieldItem.php

FieldItem is just the name of your class. You can name it whatever you want. This is just an example. FieldItem will be a class of this structure

namespace Drupal\custom_module_name\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_custom_field' field type.
 *
 * @FieldType(
 *   id = "field_custom_field",
 *   label = @Translation("Custom Field Name"),
 *   module = "custom_module_name",
 *   description = @Translation("This is just a description of your field."),
 *   default_widget = "field_custom_widget",
 *   default_formatter = "field_custom_formatter"
 * )
 */
class CustomFieldItem extends FieldItemBase {


  public static function schema(FieldStorageDefinitionInterface $field_definition)  {
       //
  }

  public function isEmpty() {
       //
  }

  public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) {
       //
  }

}

I left out the implementation of those 3 functions so that I could address them separately. Didn't want to throw out too much all at once. From here I mainly want to you to take note of the comments above the class declaration. They are very important and are used by Drupal to learn what your Field is. Most plugins use Annotation discovery so you need to get this information correct in order for it to function correctly.

  • id is the id of your custom field. You will need to use it later. Remember it.
  • label is how the field is identified in the list when you add a new field to an entity.
  • module is just the custom module that implements it.
  • description is self explanatory. Put whatever you want there.
  • default_widget is the id of a Custom Field Widget that we will create later in this tutorial.
  • default_formatter is the id of a Custom Field Formatter that we will create later in this tutorial.

Now on to the schema function. It should look something like this:

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

If you're familiar with the Drupal Schema API then it's basically the same concept here. You're essentially describing the column of the database and the TYPE of data that will be stored in the database. Also another thing I should address is that this class is basically representing ONE instance of a value. It doesn't matter if you're thinking of allowing the user to select multiple values. This field should describe of them. Anyway if you're unfamiliar with the Schema API you should check this out.

Next, the isEmpty function. Here's an example:

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

You're telling Drupal how to determine if this value is empty. If you set within the configuration that this should should be required then this will be needed to determine when to make them provide a value.

Finally, the propertyDefinitions:

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

    return $properties;
  }

This function and the schema() one sort of go hand in hand. This one sort of provides metadata for the columns defined within schema. You're essentially providing descriptive data for the items and you can also define other things here too.

That's it for the FieldItem. This is the bare minimum of what you would need to do to create a custom field type. Later I might follow up with a more advanced example.

 

Now on for the next part. We need to define a form for the user to store said input into the database. For this we need a Field Widget and the way Drupal 8 does is via another plugin of type WidgetBase. There are other types you could use but this is the most basic. Your class would need to be stored under this path:

src/Plugin/Field/FieldWidget/CustomFieldWidget.php

The class you need to create will have a structure along this lines:

namespace Drupal\custom_module_name\Plugin\Field\FieldWidget;

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

/**
 * Plugin implementation of the 'field_custom_widget' widget.
 *
 * @FieldWidget(
 *   id = "field_custom_widget",
 *   module = "custom_module_name",
 *   label = @Translation("Example Custom Widget"),
 *   field_types = {
 *     "field_custom_field"
 *   }
 * )
 */
class CustomFieldWidget extends WidgetBase {


  public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
       //
  }

  public static function validate($element, FormStateInterface $form_state) {
       //
  }

}

Yet again since this is a Plugin the Annotations are very important. Let's cover each of them:

  • id is the machine name of the widget. Remember it.
  • module is the name of the module for which it is implemented.
  • label is just a descriptive name
  • field_types is probably the most important one here. It's a list of field types that are allowed to use this widget. If you try to assign a widget to a custom type that isn't defined here you'll run into bugs.

Let's discuss the first function for our widget class:

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,
    ];

    return ['value' => $element];
  }

This function we basically create the form. If you're familiar with Drupal 8's Form API then these arrays should look familiar. There's nothing fancy going on here. We're basically accessing the FieldItemListInterface variable to check if there was a value that the user previously selected for this widget. We use the $delta variable as a reference when dealing with lists of values.

Since in this example we're dealing with a simple text value we're basically done here. But hopefully you get the idea.

 

Now let's move on with the final think we'll need a Field Formatter! We managed to develop a way to STORE the data, and then a way to SELECT our data, and finally we'll need a way to DISPLAY our data! That's where the Field Formatter comes in. Like before we'll be creating a simple class a storing it in this path:

src/Plugin/Field/FieldFormatter/CustomFieldFormatter.php

The structure will look like this:

<?php

namespace Drupal\custom_module_name\Plugin\Field\FieldFormatter;

use Drupal\Core\Field\FormatterBase;
use Drupal\Core\Field\FieldItemListInterface;

/**
 * Plugin implementation of the 'field_custom_formatter' formatter.
 *
 * @FieldFormatter(
 *   id = "field_custom_formatter",
 *   module = "custom_module_name",
 *   label = @Translation("Example Field Formatter"),
 *   field_types = {
 *     "field_custom_field"
 *   }
 * )
 */
class CustomFieldFormatter extends FormatterBase {

  /**
   * {@inheritdoc}
   */
  public function viewElements(FieldItemListInterface $items, $langcode) {
    //
  }


}

I hope you're noticing a kind of pattern here with the Annotations. They're important. But let's go through each anyway:

  • id is the machine name of the Formatter. It's important.
  • module is the name of the module for which it is implemented.
  • label is just a descriptive name
  • field_types is a list of field types this formatter will be responsible for. If your custom field is missing here then you'll run into problems.

Now let's look at that one function that will be responsible for making this formatter work:

  /**
   * {@inheritdoc}
   */
  public function viewElements(FieldItemListInterface $items, $langcode) {
    $elements = [];

    foreach ($items as $delta => $item) {
      $elements[$delta] = [
        '#type' => 'html_tag',
        '#tag' => 'p',
        '#value' => $item->value,
      ];
    }

    return $elements;
  }

This function just iterates through a potential list of values and displays them in a P tag. Very basic.

 

Anyway this essentially demonstrates how to create the most basic of Fields. In the future I may demontrate a more advanced example. One that you may see being used in production. This however I think should at least get you started