FieldTypes, FieldWidgets and FieldFormatters

Last updated on
19 October 2023

This documentation needs review. See "Help improve this page" in the sidebar.

Overview

Drupal ships with a big library of base classes which allow you to work with your very own content. When it comes to content entities you want to use Fields. It is important to understand Fields as that is where your entities store their data.

FieldTypes

Core field types:

Machine name (type) Label Description Core module
boolean Boolean An entity field containing a boolean value.
changed Last changed

An entity field containing a UNIX timestamp of when the entity has been last updated.

comment Comments

This field manages configuration and presentation of comments on an entity.

comment
created Created

An entity field containing a UNIX timestamp of when the entity has been created.

datetime Date Create and store date values. datetime
daterange Date range Create and store date ranges. datetime_range
decimal Number (decimal)

This field stores a number in the database in a fixed decimal format.

email Email An entity field containing an email value.
entity_reference Entity reference An entity field containing an entity reference.
file File

This field stores the ID of a file as an integer value.

file
file_uri File URI

An entity field containing a file URI, and a computed root-relative file URL.

file
float Number (float)

This field stores a number in the database in a floating point format.

image Image

This field stores the ID of an image file as an integer value.

image
integer Number

This field stores a number in the database as an integer.

language Language An entity field referencing a language.
link Link

Stores a URL string, optional varchar link text, and optional blob of attributes to assemble a link.

link
list_float List (float)

This field stores float values from a list of allowed 'value => label' pairs, i.e. 'Fraction': 0 => 0, .25 => 1/4, .75 => 3/4, 1 => 1.

options
list_integer List (integer)

This field stores integer values from a list of allowed 'value => label' pairs, i.e. 'Lifetime in days': 1 => 1 day, 7 => 1 week, 31 => 1 month.

options
list_string List (text)

This field stores text values from a list of allowed 'value => label' pairs, i.e. 'US States': IL => Illinois, IA => Iowa, IN => Indiana.

options
map Map

An entity field for storing a serialized array of values.

password Password An entity field containing a password value.
path Path

An entity field containing a path alias and related data.

path
string Text (plain) A field containing a plain string value.
string_long Text (plain, long) A field containing a long string value.
telephone Telephone number

This field stores a telephone number in the database.

telephone
text Text (formatted) This field stores a text with a text format. text
text_long Text (formatted, long) This field stores a long text with a text format. text
text_with_summary Text (formatted, long, with summary)

This field stores long text with a format and an optional summary.

text
timestamp Timestamp

An entity field containing a UNIX timestamp value.

uri URI An entity field containing a URI.
uuid UUID An entity field containing a UUID.

Core field formatter types:

Machine name (type) Field types Label Description
author entity_reference Author Display the referenced author user entity
comment_username string Author name Display the author name
basic_string string_long, email Plain text Plain text
boolean boolean Boolean Boolean
comment_default comment Comment list
comment_permalink string, uri Comment Permalink
content_moderation_state string Content moderation state
daterange_custom daterange Daterange Custom
daterange_default daterange Daterange Default
daterange_plain daterange Daterange Plain
datetime_custom datetime Datetime Custom
datetime_default datetime Datetime Default
datetime_plain datetime Datetime plain
datetime_time_ago datetime Time ago
number_decimal decimal, float Decimal number
file_link string File Link
entity_reference_entity_view entity_reference Rendered entity Render the referenced entity.
entity_reference_entity_id entity_reference Entity ID Display the ID of the referenced entities.
entity_reference_label entity_reference Entity Label Display the label of the referenced entities.
entity_reference_rss_category entity_reference RSS category Display reference to taxonomy term in RSS.
file_audio file Audio Display the file using an HTML5 audio tag.
file_extension string File extension
file_mime string File MIME
file_size integer File size
file_uri uri, file_uri File URI
file_video file Video Display the file using an HTML5 video tag.
file_default file Generic file
image image Image
number_integer integer Number Integer
language language Language
link link Link
link_separate link Separate link text and URL
email_mailto email Email
media_thumbnail entity_reference Thumbnail
number_unformatted integer, decimal, float Unformatted number
oembed link, string, string_long oEmbed content
list_default list_integer, list_float, list_string List Default
list_key list_integer, list_float, list_string List Key
responsive_image image Responsive image
file_rss_enclosure file RSS enclosure
string string, uri Plain text
file_table file Table of files
telephone_link telephone Telephone link
text_default text, text_long, text_with_summary Text Default
text_summary_or_trimmed text_with_summary Summary or trimmed
text_trimmed text, text_long, text_with_summary Trimmed
timestamp_ago timestamp, created, changed Time ago
timestamp timestamp, created, changed Default
uri_link uri Link to URI
file_url_plain file URL to file
user_name string User name Display the user or author name.

Core field widget types:

  • boolean_checkbox
  • comment_default
  • daterange_datelist
  • daterange_default
  • datetime_datelist
  • datetime_default
  • datetime_timestamp
  • email_default
  • entity_reference_autocomplete
  • entity_reference_autocomplete_tags
  • field_plugins_test_text_widget
  • file_generic
  • image_image
  • image_module_test_dummy_ajax_widget
  • language_select
  • layout_builder_widget
  • link_default
  • media_library_inception_widget
  • media_library_widget
  • moderation_state_default
  • number
  • oembed_textfield
  • options_buttons
  • options_select
  • path
  • shape_only_color_editable_widget
  • string_textarea
  • string_textfield
  • telephone_default
  • test_field_widget
  • test_field_widget_multilingual
  • test_field_widget_multiple
  • test_field_widget_multiple_single_value
  • text_textarea
  • text_textarea_with_summary
  • text_textfield
  • uri

Custom field types

Whenever you want to represent data in a way Drupal doesn't provide; you might want to create a new field type for your data.

Let's say you have a content entity which holds sensitive data. The creator of this content can allow specific users to access the entity via a password which differs for every user. If we talk in database tables you want to create something like this:

| entity_id | uid | password      |
-----------------------------------
| 1         | 1   | 'helloworld'  |
| 1         | 2   | 'goodbye'     |

As you can see our entity with the id 1 has two different passwords for two different users. So how can we implement it in Drupal without having to create the table all by hand? We create a new field type.

Because Drupal implements the field logic as Plugin, we always have a base class that we can inherit from to make it work in Drupal. For a new field type you want to create the following folder structure in your module:
modules/custom/MODULENAME/src/Plugin/Field/FieldType
It is quite a long path and a little bit annoying but it makes it easier for Drupal to handle all the different functions that can coexist in your modules.

For our example, we create the file EntityUserAccessField.php

namespace Drupal\MODULENAME\Plugin\Field\FieldType;
     
use Drupal\Core\Field\FieldItemBase;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\TypedData\DataDefinition;
     
/**
 * @FieldType(
 *   id = "entity_user_access",
 *   label = @Translation("Entity User Access"),
 *   description = @Translation("This field stores a reference to a user and a password for this user on the entity."),
 * )
*/
     
class EntityUserAccessField extends FieldItemBase {
  /**
   * {@inheritdoc}
   */
  public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) {
    //ToDo: Implement this.
  }
     
  /**
   * {@inheritdoc}
   */
  public static function schema(FieldStorageDefinitionInterface $field_definition) {
    //ToDo: Implement this.
  }
}

As you can see a field type looks very similar to a content entity. Actually, there is no real difference between those two but this is a topic for another node ;)

First of all, we have an Annotation for our field type

  • @FieldType: This calls the Annotation class FieldType from the Drupal library
  • id: This is the machine name of our field type so we can reuse it. Be sure not to override any predefined names from php and such.
  • label: This can be a user readable translation for the machine name.
  • description: If the label is not enough you can also add a description for the field type.

Second, our class extends FieldItemBase which makes us implement two methods so we can use this field type properly:

  • propertyDefinitions(): This method is similar to a baseFieldDefinition of a content entity (it is not the same!). We can define data which appears on entity forms where this field type is being used.
  • schema(): On entities, this method is deprecated but we still have it on fields. This method should implement the data representation of the field in a database. It can differ from the properties.

Because it is not that obvious what to write down on these methods let us add some code to them for the convenience.

public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) {
  $properties['uid'] = DataDefinition::create('integer')
      ->setLabel(t('User ID Reference'))
      ->setDescription(t('The ID of the referenced user.'))
      ->setSetting('unsigned', TRUE);

  $properties['password'] = DataDefinition::create('string')
      ->setLabel(t('Password'))
      ->setDescription(t('A password saved in plain text. That is not safe dude!'));

  $properties['created'] = DataDefinition::create('timestamp')
    ->setLabel(t('Created Time'))
    ->setDescription(t('The time that the entry was created'));

    // ToDo: Add more Properties.
 
    return $properties;
}

It is also possible to save the user id via a DataReferenceDefinition this might be covered here in the future.

public static function schema(FieldStorageDefinitionInterface $field_definition) {
  $columns = array(
    'uid' => array(
      'description' => 'The ID of the referenced user.',
      'type' => 'int',
      'unsigned' => TRUE,
    ),
    'password' => array(
      'description' => 'A plain text password.',
      'type' => 'varchar',
      'length' => 255,
    ),
    'created' => array(
      'description' => 'A timestamp of when this entry has been created.',
      'type' => 'int',
    ),

    // ToDo: Add more columns.
  );
 
  $schema = array(
    'columns' => $columns,
    'indexes' => array(),
    'foreign keys' => array(),
  );

  return $schema;
}

The schema() is necessary to make Drupal know about how to save our data. The schema columns need to be a subset of the properties defined in propertyDefinitions().

We now have a whole new field type created. It does not have any logic on it how to handle any data input but it can be used on any content entity as a field. If you want you can use it as a baseField on an entity:

public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
  // Some fields above.
 
  $fields['entity_user_access'] = BaseFieldDefinition::create('entity_user_access')
    ->setLabel(t('Entity User Access'))
    ->setDescription(t('Specify passwords for any user that want to see this entity.'))
    ->setCardinality(-1); // Ensures that you can have more than just one member
 
  // Even more fields below.
 
  return $fields;
}
  • BaseFieldDefinition::create(): You have to use the machine name of the field type in the create()
  • setCardinality(-1): The cardinality represents the amount of field data one entity can have. E.g. if we write a 2 in it only two users could access the entity, 3 would be 3 users and so on. -1 represents infinite users.

FieldWidget

If you have custom data, then you might want custom representation of this data. Widgets are used to represent how the user can input this custom data on forms. E.g

  • if an integer has to be submitted in the form but the user can only check a checkbox
  • if you want autocompletion for your data
  • if password input has to be done via a special gui

etc.

A field widget in Drupal can be found under
modules/custom/MODULENAME/src/Plugin/Field/FieldWidget
which is a very long path too. At this point, it should be clear why Drupal uses this style to separate .php files. It is very easy to overlook which files belong where.

We create a field widget in EntityUserAccessWidget.php

namespace Drupal\MODULENAME\Plugin\Field\FieldWidget;
 
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\WidgetBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\Element;
 
/**
 * Plugin implementation of the 'entity_user_access_w' widget.
 *
 * @FieldWidget(
 *   id = "entity_user_access_w",
 *   label = @Translation("Entity User Access - Widget"),
 *   description = @Translation("Entity User Access - Widget"),
 *   field_types = {
 *     "entity_user_access",
 *   },
 *   multiple_values = TRUE,
 * )
 */
 
class EntityUserAccessWidget extends WidgetBase {
  /**
   * {@inheritdoc}
   */
  public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
    // ToDo: Implement this.
  }
}

Did you notice already? Drupal 8 uses this style of code over and over again if you want to implement features. There is an annotation and a base class you have to inherit. Yay, Drupal can use it!

  • @FieldWidget: Specifies the annotation class
  • id: The machine name of the widget
  • field_types: An array of field type machine names which can use this widget
  • multiple_values: Default is FALSE. If it's true this will allow you to submit more than one value on an entity form

If you now want to use this widget with your field type you have to edit the annotation of the field type like this

// ...

/**
 * @FieldType(
 *   id = "entity_user_access",
 *   label = @Translation("Entity User Access"),
 *   description = @Translation("This field stores a reference to a user and a password for this user on the entity."),
 *   default_widget = "entity_user_access_w",
 * )
 */
     
// ...

Yeah all done! No, wait nothing happens yet because we have to implement the formElement() in our widget.

public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
    $element['userlist'] = array(
      '#type' => 'select',
      '#title' => t('User'),
      '#description' => t('Select group members from the list.'),
      '#options' => array(
         0 => t('Anonymous'),
         1 => t('Admin'),
         2 => t('foobar'),
         // This should be implemented in a better way!
       ),
  
    );
  
    $element['passwordlist'] = array(
      '#type' => 'password',
      '#title' => t('Password'),
      '#description' => t('Select a password for the user'),
    );

    //setting default value to all fields from above
    $childs = Element::children($element);
    foreach ($childs as $child) {
        $element[$child]['#default_value'] = isset($items[$delta]->{$child}) ? $items[$delta]->{$child} : NULL;
    }
   
    return $element;
}

If you now open a form with this widget then you will see at least two input fields. One is a select for the user and the other is a password field. If you want to implement how the data is being saved then you have to implement validation methods on this widget or on the entity form. See Drupal 8 Form API for more info.

By now you have done a most of the work regarding a custom field. If you don't understand what is going on then just try out the code or have a look at the core modules for deeper knowledge on the topic.

FieldFormatters

The last thing that is missing is the representation of the data in the so called view mode of an entity - by the way the widget is the form mode. This is most common if you call an entity via yourdrupalpage.com/myentity/1/view

As there is not so much to talk about here we will come directly to the code. Under modules/custom/MODULENAME/src/Plugin/Field/FieldFormatter create EntityUserAccessFormatter.php

namespace Drupal\MODULENAME\Plugin\Field\FieldFormatter;
     
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\FormatterBase;
     
/**
 * Plugin implementation of the 'entity_user_access_f' formatter.
 *
 * @FieldFormatter(
 *   id = "entity_user_access_f",
 *   label = @Translation("Entity User Access - Formatter"),
 *   description = @Translation("Entity User Access - Formatter"),
 *   field_types = {
 *     "entity_user_access",
 *   }
 * )
 */
     
class EntityUserAccessFormatter extends FormatterBase {
  /**
   * {@inheritdoc}
   */
  public function viewElements(FieldItemListInterface $items, $langcode) {
    $elements = array();
     
    foreach ($items as $delta => $item) {
      $elements[$delta] = array(
        'uid' => array(
          '#markup' => \Drupal\user\Entity\User::load($item->uid)->getUsername(),
          ),
        // Add more content
      );
    }
     
    return $elements;
  }
}

This example has a very similar annotation as the widget so we do not need to talk about it that much. The viewElements() just shows the username of the saved user id of our field type. The access implementation has to be done in the entity. So this implementation will show all the user names which have a password on the entity.

Help improve this page

Page status: Needs review

You can: