Extra Block Types (EBT) - New Layout Builder experience❗

Extra Block Types (EBT) - styled, customizable block types: Slideshows, Tabs, Cards, Accordions and many others. Built-in settings for background, DOM Box, javascript plugins. Experience the future of layout building today.

Demo EBT modules Download EBT modules

❗Extra Paragraph Types (EPT) - New Paragraphs experience

Extra Paragraph Types (EPT) - analogical paragraph based set of modules.

Demo EPT modules Download EPT modules

Scroll

FieldTypes, FieldWidgets and FieldFormatters

12/04/2025, by Ivan

Overview

Drupal 8 comes with a large library of base classes that allow you to work with your own content. When it comes to content entities, you want to use fields. It is important to understand Fields, as this is where your entities store their data.

FieldTypes

Main field types:

Custom Field Types
Whenever you want to represent data in a way Drupal doesn't provide out of the box, you may want to create a new field type for your data.

For example, if you have a content object that contains sensitive data, and the content creator wants to grant access to it using different passwords for each user, you might want to create a table like this:

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

So how can we do this in Drupal without manually creating a table? We create a new field type.

Because Drupal implements field logic as Plugins, we always have a base class we can extend to make it work with Drupal. For a new field type, you’ll want to create the following folder structure in your module:
modules/custom/MODULENAME/src/Plugin/Field/FieldType

In our example, we create a file called 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.
  }
}

This field type is very similar to a content entity. In fact, there’s no difference between them, but that’s a topic for another time ;)

First, we have an annotation for our field type:

  • @FieldType: invokes the FieldType annotation class from Drupal
  • id: the machine name of our field type
  • label: a human-readable translation of the machine name
  • description: optional extra description of the field type

Next, our class extends FieldItemBase, which requires us to implement two methods:

  • propertyDefinitions(): defines the data used in entity forms that use this field
  • schema(): defines how data is stored in the database

Let’s add code to those methods:

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'));

  return $properties;
}

We can also use DataReferenceDefinition to store user ID, which may be discussed 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',
    ),
  );
 
  return [
    'columns' => $columns,
    'indexes' => array(),
    'foreign keys' => array(),
  ];
}

Now we have a brand new field type. It lacks data handling logic, but it can be used on any content entity as a field. You can also use it as a baseField for an entity:

public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
  $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);
 
  return $fields;
}
  • Use the field type’s machine name in BaseFieldDefinition::create()
  • setCardinality(-1): allows unlimited values (unlimited users)

FieldWidget

When you have custom data, you might need a custom way for users to enter it. Field widgets are used for this in forms. For example:

  • If a form needs an integer but the user uses a checkbox
  • If you want autocomplete functionality
  • If password input has a custom UI

Field widgets live under:
modules/custom/MODULENAME/src/Plugin/Field/FieldWidget

We create our widget in EntityUserAccessWidget.php

We create our 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) {
    $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'),
    );

    // Set default value for all fields
    $children = Element::children($element);
    foreach ($children as $child) {
      $element[$child]['#default_value'] = isset($items[$delta]->{$child}) ? $items[$delta]->{$child} : NULL;
    }

    return $element;
  }
}

Notice a pattern? Drupal 8 repeatedly uses this annotation + base class structure when you implement functionality. Yes, Drupal can handle it!

  • @FieldWidget: defines the annotation class
  • id: machine name of the widget
  • field_types: an array of field type machine names compatible with this widget
  • multiple_values: default is FALSE. When TRUE, allows submitting more than one value in the entity form

If you now want to use this widget with your field type, edit the field type annotation 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",
 * )
 */

All done? Not quite — nothing will happen yet, because we still need to implement formElement() in our widget.

FieldFormatters

The last piece missing is the display of field data in the so-called view mode of the entity — whereas widgets work in form mode. You’ll typically see this when accessing a URL like yourdrupalpage.com/myentity/1/view

Let’s go straight 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 as needed
      );
    }

    return $elements;
  }
}

This example uses annotations similar to the widget and field type. The viewElements() method simply shows the username for each stored user ID. The logic for controlling access should be handled in the entity itself. This formatter will render the usernames of all users with passwords assigned to the entity.

Drupal’s online documentation is © 2000-2020 by the individual contributors and can be used in accordance with the Creative Commons License, Attribution-ShareAlike 2.0. PHP code is distributed under the GNU General Public License.