Drupal 8: creating a custom Field Type

If you need to add complex fields to a Content Type, for example a group of fields composed by one text, one number and one image, maybe with the possibility to repeat it multiple times inside a content, the simplest and fastest way to do it is using a combination of these great Drupal modules (or maybe all together also): Paragraphs, Field collection, Field group.

If you instead are searching for some special field type, you should take a look to one of these wonderful Field Types modules if you find what you need: Double field, Color field, Address, Taxonomy container, Advanced text formatter.

But if none of the modules above fits your needs, maybe because you want to create some really particular field type, or you want to have more control as possible on the field type, about it is validated, stored, edited and displayed, or you need to create only a small thing and you don’t want to install a generic module to do it, or for some other reason, then you may want to create your own custom Field Type.

Define a custom Field Type

Create a new custom module

A Field Type in Drupal is defined as a module, then we need to create a new custom module.

Create the modules/custom directory if it doesn’t exists.

Inside the custom directory create the following folder structure that will contains the Field Type, in this case named address:

address/
  + src/
  |   + Plugin/
  |   |   + Field/
  |   |   |   + FieldFormatter/
  |   |   |   + FieldType/
  |   |   |   + FieldWidget/
  + address.info.yml

IMPORTANT: the module name must be lower case otherwise Drupal will ignore it.

File .info.yml

Inside the file with extension .info.yml we set some informations about the module, as the module name, the description, the package that must be Field types and the list of dependencies that must contains the value field:

name: Address
description: Defines a simple address field type.

# The type must be 'module' and the package must be 'Field types'
type: module
package: Field types
core: 8.x

# The list of dependencies must contains the 'field' value
dependencies:
  - field

Field type

Inside the folder src/Plugin/Field/FieldType create a new php file that will contains the Field Type definition:

src/Plugin/Field/FieldType/Address.php
<?php

namespace Drupal\address\Plugin\Field\FieldType;

use Drupal\Core\Field\FieldItemBase;
use Drupal\Core\TypedData\DataDefinition;
use Drupal\Core\Field\FieldStorageDefinitionInterface as StorageDefinition;

/**
 * Plugin implementation of the 'address' field type.
 *
 * @FieldType(
 *   id = "Address",
 *   label = @Translation("Address"),
 *   description = @Translation("Stores an address."),
 *   category = @Translation("Custom"),
 *   default_widget = "AddressDefaultWidget",
 *   default_formatter = "AddressDefaultFormatter"
 * )
 */
class Address extends FieldItemBase {

  /**
   * Field type properties definition.
   * 
   * Inside this method we defines all the fields (properties) that our 
   * custom field type will have.
   * 
   * Here there is a list of allowed property types: https://goo.gl/sIBBgO
   */
  public static function propertyDefinitions(StorageDefinition $storage) {

    $properties = [];

    $properties['street'] = DataDefinition::create('string')
      ->setLabel(t('Street'));

    $properties['city'] = DataDefinition::create('string')
      ->setLabel(t('City'));

    return $properties;
  }

  /**
   * Field type schema definition.
   * 
   * Inside this method we defines the database schema used to store data for 
   * our field type.
   * 
   * Here there is a list of allowed column types: https://goo.gl/YY3G7s
   */
  public static function schema(StorageDefinition $storage) {

    $columns = [];
    $columns['street'] = [
      'type' => 'char',
      'length' => 255,
    ];
    $columns['city'] = [
      'type' => 'char',
      'length' => 255,
    ];

    return [
      'columns' => $columns,
      'indexes' => [],
    ];
  }

  /**
   * Define when the field type is empty. 
   * 
   * This method is important and used internally by Drupal. Take a moment
   * to define when the field fype must be considered empty.
   */
  public function isEmpty() {

    $isEmpty = 
      empty($this->get('street')->getValue()) &&
      empty($this->get('city')->getValue());

    return $isEmpty;
  }

} // class

In the comment above the class there is the @FieldType annotation. This annotation defines the new Field Type and you must set some properties here, as a field type id, a label, etc, and two properties, default_widget and default_formatter, that are respectively a widget for editing the field type and a formatter for displaying the field type. We define both the widget and the formatter in a moment.

The class Address defines the custom Field Type: its properties (fields) with the method propertyDefinitions, how the properties should be stored in the database in the method schema and the definition of empty field with the method isEmpty.

Inside this class you can define also some other logic for the Field Type, as the validation logic with the getConstraints method. Here there is an example.

Field widget

Inside the folder src/Plugin/Field/FieldWidget create a new php file containing the field widget definition:

src/Plugin/Field/FieldWidget/AddressDefaultWidget.php
<?php

namespace Drupal\address\Plugin\Field\FieldWidget;

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

/**
 * Plugin implementation of the 'AddressDefaultWidget' widget.
 *
 * @FieldWidget(
 *   id = "AddressDefaultWidget",
 *   label = @Translation("Address select"),
 *   field_types = {
 *     "Address"
 *   }
 * )
 */
class AddressDefaultWidget extends WidgetBase {

  /**
   * Define the form for the field type.
   * 
   * Inside this method we can define the form used to edit the field type.
   * 
   * Here there is a list of allowed element types: https://goo.gl/XVd4tA
   */
  public function formElement(
    FieldItemListInterface $items,
    $delta, 
    Array $element, 
    Array &$form, 
    FormStateInterface $formState
  ) {

    // Street

    $element['street'] = [
      '#type' => 'textfield',
      '#title' => t('Street'),

      // Set here the current value for this field, or a default value (or 
      // null) if there is no a value
      '#default_value' => isset($items[$delta]->street) ? 
          $items[$delta]->street : null,

      '#empty_value' => '',
      '#placeholder' => t('Street'),
    ];

    // City

    $element['city'] = [
      '#type' => 'textfield',
      '#title' => t('City'),
      '#default_value' => isset($items[$delta]->city) ? 
          $items[$delta]->city : null,
      '#empty_value' => '',
      '#placeholder' => t('City'),
    ];

    return $element;
  }

} // class

The @FieldWidget annotation defines the field widget. In this annotation you should set the widget id that is the value used for the default_widget property in the @FieldType annotation. We used the class name as id but you can use also some different name. The field_types property should list all the field types allowed for this widget, and here we can set the field type id previously defined.

With the formElement method you can define the form that will be used to edit the Address field type. As for the field type definition, you can define complex logic here and create special forms for your field type. Some examples are here and here.

Field formatter

Inside the folder src/Plugin/Field/FieldFormatter create a new php containing the field formatter definition:

src/Plugin/Field/FieldFormatter/AddressDefaultFormatter.php
<?php

namespace Drupal\address\Plugin\Field\FieldFormatter;

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

/**
 * Plugin implementation of the 'AddressDefaultFormatter' formatter.
 *
 * @FieldFormatter(
 *   id = "AddressDefaultFormatter",
 *   label = @Translation("Address"),
 *   field_types = {
 *     "Address"
 *   }
 * )
 */
class AddressDefaultFormatter extends FormatterBase {

  /**
   * Define how the field type is showed.
   * 
   * Inside this method we can customize how the field is displayed inside 
   * pages.
   */
  public function viewElements(FieldItemListInterface $items, $langcode) {

    $elements = [];
    foreach ($items as $delta => $item) {
      $elements[$delta] = [
        '#type' => 'markup',
        '#markup' => $item->street . ', ' . $item->city
      ];
    }

    return $elements;
  }
  
} // class

The @FieldFormatter annotation defines the field formatter and it is very similar to the @FieldWidget annotation.

With the viewElements method you can define how the field type will be displayed inside a page. If you want to use some HTML to display the field type you can also use a custom theme for this, and avoid to put HTML tags inside the PHP code. Here you can find an example using a theme.

Use the Field Type

Once created the Field Type you should install the module in Drupal 8 then you can use your custom Field Type.

Install with the admin panel

Go to Extends, search your module in the list, select it, then click on “Install” at the bottom of the page.

Install with Drupal console

If you installed the Drupal console tool you can use these commands to install and uninstall your Field Type (module):

$ # Install a module
$ drupal module:install [module_name]

$ # Remove a module
$ drupal module:uninstall [module_name]

Get the code from GitHub

You can get the code used in this post from our GitHub repository here: https://github.com/netgloo/drupal-samples/tree/master/address

References

Tutorials
http://www.ixis.co.uk/blog/drupal-8-creating-field-types-multiple-values
https://www.computerminds.co.uk/drupal-code/drupal-8-creating-custom-field-part-1-field-type
http://capgemini.github.io/drupal/writing-custom-fields-in-drupal-8/
https://www.drupal.org/node/2302735
https://www.drupal.org/node/2620964

Properties, columns and form elements types
https://goo.gl/XVd4tA
https://www.drupal.org/node/159605
https://api.drupal.org/api/drupal/elements

Other references
https://api.drupal.org/api/drupal/core!modules!field!field.api.php/group/field_types/8
https://www.drupal.org/node/2116781
http://www.sitepoint.com/creating-custom-field-formatters-drupal-8/
https://api.drupal.org/api/drupal/core!lib!Drupal!Core!Database!database.api.php/group/schemaapi/8

  • Scarwr McScary

    Fatal error: Cannot declare class DrupalsacfieldssubjectPluginFieldTypeSacFieldsSubject, because the name is already in use in /home/sidelinesac/public_html/modules/custom/sacfieldssubject/src/Plugin/Field/FieldType/SacFieldsSubject.php on line 118 I am receiving that when I try to use my field type.

  • Nicola

    HI, what if i need to use a list field? How can i set it up?

  • Lalit Garg

    hello i implement this it is working properly but i custom this for select box and it is working fine for single select but if i make it multiselect type i got this error when i save this setting
    This value should be of the correct primitive type.

    Please help me in the same wht and where i missing and wht shoud i have to chnge for this i hve spend lots of tym on google but found nothing it would be helpful for me if u will help me

  • Critter Power

    How Do I add an image to this field?

Categories

Category BootstrapCategory CoffeescriptCategory DrupalCategory GravCategory HTMLCategory JavascriptCategory JoomlaCategory jQueryCategory LaravelCategory MagentoCategory PHPCategory SharePointCategory SpringCategory ThymeleafCategory WordPressCategory Workflow

Comments

Developed and designed by Netgloo
© 2019 Netgloo