Every field type you've ever added to a content type — text, number, image, entity reference — is powered by three separate, cooperating plugins: a Field Type (what gets stored), a Field Widget (how it's entered), and a Field Formatter (how it's displayed). This lesson covers the first and most foundational of the three: defining what a field actually stores.
What you'll learn in this lesson
- Why field storage is split into its own plugin, separate from widgets and formatters
- The two methods every field type must implement:
schema()andpropertyDefinitions() - How
default_widgetanddefault_formatterconnect a field type to its companion plugins
Why storage is its own plugin
Splitting storage, input, and display into three separate plugin types means each can vary independently. The same RGB color value (storage) could be entered through three red/green/blue textboxes, a color-picker widget, or a plain hex textfield (three different widgets) — and displayed as a swatch, a hex code, or a color name (three different formatters) — all without touching the storage definition at all. This is the same "swappable implementations of a shared concept" idea from earlier lessons, applied three times to one feature.
Defining a field type: the @FieldType annotation
/**
* Plugin implementation of the 'field_example_rgb' field type.
*
* @FieldType(
* id = "field_example_rgb",
* label = @Translation("Example Color RGB"),
* module = "field_example",
* description = @Translation("Demonstrates a field composed of an RGB color."),
* default_widget = "field_example_text",
* default_formatter = "field_example_simple_text"
* )
*/
class RgbItem extends FieldItemBase {
default_widget and default_formatter are the connective tissue between the three plugin types: when a site builder first adds this field to a content type, Drupal pre-selects these as the widget and formatter, though either can be swapped later for any other widget/formatter that declares compatibility with field_example_rgb.
schema() — what actually gets stored
public static function schema(FieldStorageDefinitionInterface $field_definition) {
return [
'columns' => [
'value' => [
'type' => 'text',
'size' => 'tiny',
'not null' => FALSE,
],
],
];
}
This is a genuine database schema fragment — Drupal uses it to create the actual column(s) in the field's storage table when the field is first attached to an entity type. This RGB field needs exactly one column, a short text string (it stores a hex code like #ff5733). A more complex field type — say, a geographic coordinate — might declare two columns here, one each for latitude and longitude.
propertyDefinitions() — describing that data to the rest of Drupal
public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) {
$properties['value'] = DataDefinition::create('string')
->setLabel(t('Hex value'));
return $properties;
}
Where schema() describes the database column, propertyDefinitions() describes that same data to Drupal's Typed Data API — the system Views, the Entity API, and validation constraints all use to understand what a field's value actually represents, independent of how it's stored. The key ('value') must match the column name declared in schema().
isEmpty(). Every field type must implement this so Drupal knows whether a given entity's field value counts as "set" or "blank" — field_example_rgb checks whether its value property is NULL or an empty string. Get this wrong and required-field validation, and "hide empty fields" display logic, will behave incorrectly.Quick check: could two different field types both declare
default_widget = "field_example_text"? (Yes — a widget only needs to declare which field types it's compatible with via its ownfield_typesannotation property; nothing stops multiple field types from independently naming the same widget as their default, as long as that widget actually supports both.)
Key takeaways
- Fields are three cooperating plugins — Field Type (storage), Field Widget (input), Field Formatter (display) — deliberately kept independent so each can vary on its own.
schema()defines the real database column(s);propertyDefinitions()describes that same data to the Typed Data API.default_widget/default_formatterpre-select companions when a field is first added, but don't lock a site builder into them.isEmpty()is required on every field type and directly affects required-field validation and empty-field display logic.
Coming up next
Storage defined — next you'll build the second of the three field plugins: a custom Field Widget that controls exactly how an editor enters this data on a content form.