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

Defining and Using Content Entity Field Definitions

12/04/2025, by Ivan

Defining and Using Content Entity Field Definitions

Content entities must explicitly define all their fields by providing field definitions in the entity class. Field definitions are based on the Typed Data API (see also How Entities Implement It).

Field Definitions

Entity types define their base fields in a static method within the entity class. Base fields are non-configurable fields that always exist on a given entity type, such as the node title or creation/modification dates. The entity manager complements these fields with configurable and non-configurable fields provided by other modules using hook_entity_field_info() and hook_entity_field_info_alter(). Field UI-configured fields are also added similarly (these hooks no longer exist in the new API).

Field definitions are simple objects implementing the FieldDefinitionInterface. Base fields are typically created using the BaseFieldDefinition class. Configurable fields implement the interface using associated configuration objects (Field and FieldInstance).

Field definitions are also where validation constraints for field elements or sub-properties are defined. Any field type plugin can be used here.

All fields (including base fields) are always lists of field items, meaning that the FieldItem class defined by the field type is wrapped in a FieldItemList class representing a list of field items.

All fields can have widgets and formatters for editing and display.

Base Fields

Below is a shortened example list of base field definitions for the Node entity type.

use Drupal\Core\Field\BaseFieldDefinition;

class Node implements NodeInterface {

  public static function baseFieldDefinitions($entity_type) {
    $fields['nid'] = BaseFieldDefinition::create('integer')
      ->setLabel(t('Node ID'))
      ->setDescription(t('The node ID.'))
      ->setReadOnly(TRUE);

    $fields['uuid'] = BaseFieldDefinition::create('uuid')
      ->setLabel(t('UUID'))
      ->setDescription(t('The node UUID.'))
      ->setReadOnly(TRUE);

    $fields['langcode'] = BaseFieldDefinition::create('language')
      ->setLabel(t('Language code'))
      ->setDescription(t('The node language code.'));

    $fields['title'] = BaseFieldDefinition::create('string')
      ->setLabel(t('Title'))
      ->setDescription(t('The title of this node, always treated as non-markup plain text.'))
      ->setRequired(TRUE)
      ->setTranslatable(TRUE)
      ->setSettings([
        'default_value' => '',
        'max_length' => 255,
      ]);

    $fields['uid'] = BaseFieldDefinition::create('entity_reference')
      ->setLabel(t('User ID'))
      ->setDescription(t('The user ID of the node author.'))
      ->setSettings([
        'target_type' => 'user',
        'default_value' => 0,
      ]);

    $fields['changed'] = BaseFieldDefinition::create('changed')
      ->setLabel(t('Changed'))
      ->setDescription(t('The time that the node was last edited.'));

    return $fields;
  }
}

Multivalue Fields

To define the maximum number of items allowed for a field, call setCardinality().

->setCardinality(3);

To allow unlimited values:

->setCardinality(FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED);

Example for a multivalue field referencing user entities:

$fields['my_field'] = BaseFieldDefinition::create('entity_reference')
  ->setLabel(t('The label of the field'))
  ->setDescription(t('The description of the field.'))
  ->setRevisionable(TRUE)
  ->setSetting('target_type', 'user')
  ->setSetting('handler', 'default')
  ->setTranslatable(TRUE)
  ->setDisplayOptions('view', [
    'label' => 'hidden',
    'type' => 'author',
    'weight' => 0,
  ])
  ->setDisplayOptions('form', [
    'type' => 'entity_reference_autocomplete',
    'weight' => 5,
    'settings' => [
      'match_operator' => 'CONTAINS',
      'size' => '60',
      'autocomplete_type' => 'tags',
      'placeholder' => '',
    ],
  ])
  ->setDisplayConfigurable('form', TRUE)
  ->setDisplayConfigurable('view', TRUE)
  ->setRequired(TRUE)
  ->setCardinality(FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED);

Entities can also provide bundle-specific fields or modify base fields per bundle (e.g., node title label can differ per bundle). Clone the base field definition before modifying it to prevent changes from affecting all bundles.

use Drupal\node\Entity\NodeType;

public static function bundleFieldDefinitions(EntityTypeInterface $entity_type, $bundle, array $base_field_definitions) {
  $node_type = NodeType::load($bundle);
  $fields = [];
  if (isset($node_type->title_label)) {
    $fields['title'] = clone $base_field_definitions['title'];
    $fields['title']->setLabel($node_type->title_label);
  }
  return $fields;
}

Field Types

Drupal core provides a list of field types available for use in base fields. Additionally, modules can define custom field types that may also be used.

  • string: A simple string.
  • boolean: A boolean value stored as an integer.
  • integer: An integer with options to validate min/max values (decimal and float are also available).
  • decimal: A decimal number with configurable precision and scale.
  • float: A floating-point number.
  • language: Stores a language code and computed property for language.
  • timestamp: A Unix timestamp stored as an integer.
  • created: A timestamp that defaults to the current time.
  • changed: A timestamp automatically updated when the entity is saved.
  • datetime: Date stored in ISO 8601 string format.
  • uri: Stores a URI. The link module provides a link field type which can include a title and point to internal or external routes.
  • uuid: A UUID field that auto-generates a new UUID by default.
  • email: An email field with validation, widgets, and formatters.
  • entity_reference: Reference to another entity using target_id and computed entity properties. The entity_reference module provides related widgets and formatters.
  • map: Holds an arbitrary number of properties, stored as a serialized string.

Configurable Fields

Additional fields can be registered via hook_entity_base_field_info() and hook_entity_bundle_field_info(). Below are examples of registering base and bundle fields.

use Drupal\Core\Field\BaseFieldDefinition;

function path_entity_base_field_info(EntityTypeInterface $entity_type) {
  if ($entity_type->id() === 'taxonomy_term' || $entity_type->id() === 'node') {
    $fields['path'] = BaseFieldDefinition::create('path')
      ->setLabel(t('The path alias'))
      ->setComputed(TRUE);

    return $fields;
  }
}

function field_entity_bundle_field_info(EntityTypeInterface $entity_type, $bundle, array $base_field_definitions) {
  if ($entity_type->isFieldable()) {
    return Field::fieldInfo()->getBundleInstances($entity_type->id(), $bundle);
  }
}

Alter hooks exist for both of the above examples.

Field Storage

If your field has no special storage requirements, the Entity Field API will handle database schema creation and updates. This is the default behavior for fields that are not computed (setComputed(TRUE)) or that do not explicitly request custom storage (setCustomStorage(TRUE)).

For example, to add a boolean field to all node entities that indicates whether the content is highlighted:

function MYMODULE_entity_base_field_info(EntityTypeInterface $entity_type) {
  $fields = [];

  if ($entity_type->id() === 'node') {
    $fields['highlight'] = BaseFieldDefinition::create('boolean')
      ->setLabel(t('Highlight'))
      ->setDescription(t('Whether or not the node is highlighted.'))
      ->setRevisionable(TRUE)
      ->setTranslatable(TRUE)
      ->setDisplayOptions('form', [
        'type' => 'boolean_checkbox',
        'settings' => [
          'display_label' => TRUE,
        ],
      ])
      ->setDisplayConfigurable('form', TRUE);
  }

  return $fields;
}

Sometimes running update.php does not apply schema changes. Instead, execute:

\Drupal::entityTypeManager()->clearCachedDefinitions();
\Drupal::service('entity.definition_update_manager')->applyUpdates();

Note: This triggers updates for any pending field definition changes.

Update: The above may not work in Drupal 8.7+

See this change record for the updated approach.

Installing New Field Storage Definitions

function example_update_8701() {
  $field_storage_definition = BaseFieldDefinition::create('boolean')
    ->setLabel(t('Revision translation affected'))
    ->setDescription(t('Indicates if the last edit of a translation belongs to current revision.'))
    ->setReadOnly(TRUE)
    ->setRevisionable(TRUE)
    ->setTranslatable(TRUE);

  \Drupal::entityDefinitionUpdateManager()
    ->installFieldStorageDefinition('revision_translation_affected', 'block_content', 'block_content', $field_storage_definition);
}

If your custom module adds a new field, it will be added automatically when the module is enabled and removed when it is uninstalled.

If the module is already installed and you need to write a hook_update_N to update the field definitions, use:

function MYMODULE_update_8001() {
  $entity_type = \Drupal::service('entity_type.manager')->getDefinition('node');
  \Drupal::service('entity.definition_update_manager')->updateEntityType($entity_type);
}

Or:

function node_update_8001() {
  $storage_definition = BaseFieldDefinition::create('boolean')
    ->setLabel(t('Revision translation affected'))
    ->setDescription(t('Indicates if the last edit of a translation belongs to current revision.'))
    ->setReadOnly(TRUE)
    ->setRevisionable(TRUE)
    ->setTranslatable(TRUE);

  \Drupal::entityDefinitionUpdateManager()
    ->installFieldStorageDefinition('revision_translation_affected', 'node', 'node', $storage_definition);
}

See https://www.drupal.org/node/2554097 for more examples and best practices.

Working with Field Definitions

Note: Since entities are complex data objects, they implement ComplexDataInterface. Within the typed data system, every typed sub-element is a property. This imposes a naming structure that can be lifted with custom handling.

// Check if an entity has a specific field.
$entity->hasField('field_tags');

// Get all field definitions.
$field_definitions = $entity->getFieldDefinitions();

// Get all field item property definitions for a field (e.g., 'image').
$property_definitions = $entity->image->getFieldDefinition()->getPropertyDefinitions();

// Get a specific property definition.
$alt_definition = $entity->image->getFieldDefinition()->getPropertyDefinition('alt');

// Get all field storage definitions across all bundles.
\Drupal::service('entity_field.manager')->getFieldStorageDefinitions($entity_type);

// Get field definitions for a specific bundle.
\Drupal::service('entity_field.manager')->getFieldDefinitions($entity_type, $bundle);

Widgets and Formatters for Base Fields

Base fields can define their widgets and formatters just like configurable fields. These are set using methods on the FieldDefinition object:

use Drupal\Core\Field\BaseFieldDefinition;

$fields['title'] = BaseFieldDefinition::create('string')
  ->setLabel(t('Title'))
  ->setDescription(t('The title of this node, always treated as non-markup plain text.'))
  ->setRequired(TRUE)
  ->setTranslatable(TRUE)
  ->setSettings([
    'default_value' => '',
    'max_length' => 255,
  ])
  ->setDisplayOptions('view', [
    'label' => 'hidden',
    'type' => 'string',
    'weight' => -5,
  ])
  ->setDisplayOptions('form', [
    'type' => 'string',
    'weight' => -5,
  ])
  ->setDisplayConfigurable('form', TRUE);

This uses the string formatter and widget and sets the display weight for the node title. Use setDisplayConfigurable() to expose the field in the UI for adjusting order and label display. You can also set the region to hidden via the display options to hide the field by default.

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.