Custom field types
A custom field type lets site builders attach your data type to any entity in the UI, the same way they add a text or number field. It comes in three plugins: the field type (storage and shape), a widget (how it is edited), and a formatter (how it is displayed). The worked example is a hex color field.
Ship a reusable field type rather than a one-off database column. Reach for this when several entities need the same structured value and editors should manage it; for a single entity’s internal data, a base field on a custom entity is simpler.
Prerequisites
Section titled “Prerequisites”- A custom module, per Scaffold a module.
- Entities, bundles, and fields for the field model.
- Plugins, since all three pieces are plugins.
Define the field type
Section titled “Define the field type”The field type declares its typed-data properties and its database schema. Put it in src/Plugin/Field/FieldType/, mark it with the FieldType attribute, and name the default widget and formatter you build below.
<?php
declare(strict_types=1);
namespace Drupal\howto_field_type\Plugin\Field\FieldType;
use Drupal\Core\Field\Attribute\FieldType;use Drupal\Core\Field\FieldItemBase;use Drupal\Core\Field\FieldStorageDefinitionInterface;use Drupal\Core\StringTranslation\TranslatableMarkup;use Drupal\Core\TypedData\DataDefinition;
/** * Defines the 'color_hex' field type: a single hex color string. */#[FieldType( id: 'color_hex', label: new TranslatableMarkup('Color (hex)'), description: new TranslatableMarkup('Stores a hex color like #0678be.'), default_widget: 'color_hex_default', default_formatter: 'color_hex_swatch',)]final class ColorHexItem extends FieldItemBase {
/** * The properties (the typed-data shape) of one field value. */ public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition): array { $properties['value'] = DataDefinition::create('string') ->setLabel(new TranslatableMarkup('Hex color')) ->setRequired(TRUE); return $properties; }
/** * The database columns backing those properties. */ public static function schema(FieldStorageDefinitionInterface $field_definition): array { return [ 'columns' => [ 'value' => [ 'type' => 'varchar', 'length' => 7, ], ], ]; }
/** * An item with no value is treated as empty (and not saved). */ public function isEmpty(): bool { $value = $this->get('value')->getValue(); return $value === NULL || $value === ''; }
}propertyDefinitions() describes the value to the typed-data system; schema() describes the columns to the database. isEmpty() decides when an item is skipped on save; get it wrong and you store blank rows.
Define the widget
Section titled “Define the widget”The widget renders the edit element for one field item. Put it in src/Plugin/Field/FieldWidget/, and list the field types it serves.
<?php
declare(strict_types=1);
namespace Drupal\howto_field_type\Plugin\Field\FieldWidget;
use Drupal\Core\Field\Attribute\FieldWidget;use Drupal\Core\Field\FieldItemListInterface;use Drupal\Core\Field\WidgetBase;use Drupal\Core\Form\FormStateInterface;use Drupal\Core\StringTranslation\TranslatableMarkup;
/** * Edits a color_hex value with the HTML5 color picker. */#[FieldWidget( id: 'color_hex_default', label: new TranslatableMarkup('Color picker'), field_types: ['color_hex'],)]final class ColorHexWidget extends WidgetBase {
/** * Returns the form element for one field item (delta). */ public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state): array { $element['value'] = $element + [ '#type' => 'color', '#default_value' => $items[$delta]->value ?? '#000000', ]; return $element; }
}formElement() returns a render element keyed by the property it edits (value), pre-filled from the current item.
Define the formatter
Section titled “Define the formatter”The formatter turns stored values into a render array. Put it in src/Plugin/Field/FieldFormatter/.
<?php
declare(strict_types=1);
namespace Drupal\howto_field_type\Plugin\Field\FieldFormatter;
use Drupal\Core\Field\Attribute\FieldFormatter;use Drupal\Core\Field\FieldItemListInterface;use Drupal\Core\Field\FormatterBase;use Drupal\Core\StringTranslation\TranslatableMarkup;
/** * Renders each color_hex value as a labelled swatch. */#[FieldFormatter( id: 'color_hex_swatch', label: new TranslatableMarkup('Swatch'), field_types: ['color_hex'],)]final class ColorHexFormatter extends FormatterBase {
/** * Builds a render array for the whole field item list. */ public function viewElements(FieldItemListInterface $items, $langcode): array { $elements = []; foreach ($items as $delta => $item) { $value = (string) $item->get('value')->getValue(); $elements[$delta] = [ '#type' => 'html_tag', '#tag' => 'span', '#value' => $value, '#attributes' => [ 'class' => ['color-hex-swatch'], 'style' => 'background-color:' . $value, ], ]; } return $elements; }
}Verify
Section titled “Verify”Install the module, then add the field and check it round-trips:
ddev drush pm:install howto_field_typeddev drush php:eval " \Drupal\field\Entity\FieldStorageConfig::create(['field_name' => 'field_color', 'entity_type' => 'node', 'type' => 'color_hex'])->save(); print 'created';"Add a “Color (hex)” field to a content type at Structure → Content types → Manage fields; the color picker appears on the edit form and the swatch on display. The kernel test in the example module proves the value round-trips through storage.
Variations
Section titled “Variations”Settings
Section titled “Settings”Add defaultStorageSettings() / storageSettingsForm() to the field type, and defaultSettings() / settingsForm() to the widget or formatter, to expose per-field options.
Validation
Section titled “Validation”Override getConstraints() on the field type to enforce rules (a valid hex pattern, here). Core’s EmailItem is a concise model.
Related
Section titled “Related”- Custom entity types for storing structured data without a reusable field.
- Entities, bundles, and fields for the field model.
- The
field types topicon api.drupal.org.
Was this page helpful?
Drupal is a registered trademark of Dries Buytaert.