Updating Entities and Fields in Drupal 8

Last updated on
13 November 2025

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

If your module is making a data model change related to entities and fields, then you will need to write a hook_update_N() function that will update the sites for existing users of your module who already had it installed before you made the change, so that they can continue to function. This is described below.

General notes

Some notes on hook_update_N() functions:

  • The hook_update_N() skeleton section on the parent page tells how/where to create your hook_update_N() function.
  • Combine all your current data model updates into one hook_update_N() function, but don't combine them with other updates that were done previously.

Core issue discussion

A discussion to add helpers for better fields DX into core can be found here: #937442: Field type modules cannot maintain their field schema (field type schema change C(R)UD is needed)

Example: adding a new base field to an entity type

The field definition has to be repeated from the entity class, as the definition in the entity class can't be relied on during the update process:

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

Example: updating a field from an obsolete type to a new type

As one example of a more drastic change that wasn't handled by the automatic update system that handles entity and field schema updates, on issue #1847596: Remove Taxonomy term reference field in favor of Entity reference, the Taxonomy Reference field was removed, in place of using the more generic Entity Reference field. This change was not a simple schema update, so the automatic system didn't take care of it. At the time of that change, Drupal Core was not attempting to provide updates for data model changes, but the contributed HEAD to HEAD module was.

So, the HEAD to HEAD project provided a function called head2head_1847596() (in head2head.module) to update an existing site's taxonomy reference fields to be generic entity reference fields.

This function is a bit complex, and consists of several parts. Each part does operations similar to those shown on the page about updating configuration in Drupal 8.

Updating field storage config items

In this section, the function locates all the config items of type 'taxonomy_term_reference', and updates them to be entity reference fields instead.

  if (!$field_storage_configs = \Drupal::entityTypeManager()->getStorage('field_storage_config')->loadByProperties(array('type' => 'taxonomy_term_reference'))) {
    return;
  }

  foreach ($field_storage_configs as $field_storage) {
    // Since the usual workflow for field storages do not allow changing the
    // field type, we have to work around it in this case.
    $new_field_storage = $field_storage->toArray();
    $new_field_storage['type'] = 'entity_reference';
    $new_field_storage['module'] = 'core';
    $new_field_storage['settings']['target_type'] = 'taxonomy_term';

    $vocabulary_name = $new_field_storage['settings']['allowed_values'][0]['vocabulary'];
    unset($new_field_storage['settings']['allowed_values']);

    $new_field_storage = FieldStorageConfig::create($new_field_storage);
    $new_field_storage->original = $new_field_storage;
    $new_field_storage->enforceIsNew(FALSE);

    $new_field_storage->save();

Updating field config items

In this section, the function locates all field config items for the storage config item it's working on, and updates these.

    $field_name = $field_storage->getName();
    if (!$fields = \Drupal::entityTypeManager()->getStorage('field_config')->loadByProperties(['field_name' => $field_name])) {
      continue;
    }

    foreach ($fields as $field) {
      $new_field = $field->toArray();
      $new_field['field_type'] = 'entity_reference';
      $new_field['settings'] = [
        'handler' => 'default:taxonomy_term',
        'handler_settings' => [
          'target_bundles' => [$vocabulary_name => $vocabulary_name],
          // Enable auto-create.
          'auto_create' => TRUE,
        ],
      ];

      $new_field = FieldConfig::create($new_field);
      $new_field->original = $field;
      $new_field->enforceIsNew(FALSE);
      $new_field->save();

Updating entity view display configs

In this section, the function locates all entity views display config items that are displaying the field being worked on, and updates them.

      $properties = [
        'targetEntityType' => $field->getTargetEntityTypeId(),
        'bundle' => $field->getTargetBundle()
      ];
      if ($view_displays = \Drupal::entityTypeManager()->getStorage('entity_view_display')->loadByProperties($properties)) {
        foreach ($view_displays as $view_display) {
          if ($component = $view_display->getComponent($field_name)) {
            // Map taxonomy term reference formatters to entity reference ones.
            switch ($component['type']) {
              case 'taxonomy_term_reference_plain':
                $type = 'entity_reference_label';
                $settings = [
                  'link' => FALSE,
                ];
                break;
              case 'taxonomy_term_reference_link':
                $type = 'entity_reference_label';
                $settings = [
                  'link' => TRUE,
                ];
                break;
              case 'taxonomy_term_reference_rss_category':
                $type = 'entity_reference_rss_category';
                $settings = [];
                break;
            }

            $view_display->setComponent($field_name, [
              'type' => $type,
              'settings' => $settings,
            ] + $component)->save();
          }
        }
      }

Note: Ensure your schema definitions are updated to match the new settings.

Updating entity form configs

In this section, the function locates all entity form config items that contain widgets for the field being worked on, and updates them.

      $properties = array(
        'targetEntityType' => $field->getTargetEntityTypeId(),
        'bundle' => $field->getTargetBundle()
      );
      if ($form_displays = \Drupal::entityTypeManager()->getStorage('entity_form_display')->loadByProperties($properties)) {
        foreach ($form_displays as $form_display) {
          if ($component = $form_display->getComponent($field_name)) {
            $form_display->setComponent($field_name, [
              'type' => 'entity_reference_autocomplete_tags',
              'settings' => [
                'match_operator' => 'CONTAINS',
                'size' => '60',
                'placeholder' => '',
              ],
            ] + $component)->save();
          }
        }
      }
    }
  }

Note: Ensure your schema definitions are updated to match the new settings.

Example: Updating name and description of Entity programatically.

// To update name and description of content type Page 
$configEntity = \Drupal::entityTypeManager()
    ->getStorage('node_type')
    ->load('page');
if (!$configEntity) {
  return NULL;
}
$configEntity->set('name', 'New name page');
$configEntity->set('description', 'New updated description');
$configEntity->save();

Other examples

Updating a base field type

If you need to update the type of a custom BaseFieldDefinition, the general process after having updated the entity class itself is:

  1. Store the existing values for the field
  2. Clear out the values from the field (required to uninstall a field)
  3. Uninstall the field
  4. Create a new BaseFieldDefinition reflecting the entity class update
  5. Install the new definition
  6. Restore the values from step 1

For example, changing the type from `boolean` to `string`:

$database = \Drupal::database();
$transaction = $database->startTransaction();

$entity_type_manager = \Drupal::entityTypeManager();
$bundle_of = 'node';

$storage = $entity_type_manager->getStorage($bundle_of);
$bundle_definition = $entity_type_manager->getDefinition($bundle_of);
// Sometimes the primary key isn't 'id'. e.g. 'eid' or 'item_id'.
$id_key = $bundle_definition->getKey('id');
// If there is no data table defined then use the base table.
$table_name = $storage->getDataTable() ?: $storage->getBaseTable();
$definition_manager = \Drupal::entityDefinitionUpdateManager();

// Store the existing values.
$status_values = $database->select($table_name)
  ->fields($table_name, [$id_key, 'status_field'])
  ->execute()
  ->fetchAllKeyed();

// Clear out the values.
$database->update($table_name)
  ->fields(['status_field' => NULL])
  ->execute();

// Uninstall the field.
$field_storage_definition = $definition_manager->getFieldStorageDefinition('status_field', $bundle_of);
$definition_manager->uninstallFieldStorageDefinition($field_storage_definition);

// Create a new field definition.
$new_status_field = BaseFieldDefinition::create('string')
  ->setLabel(t('Status field'))
  ->setDescription(t('The status - either no, yes or skip.'))
  ->setDefaultValue('no')
  ->setRevisionable(FALSE)
  ->setTranslatable(FALSE);

// Install the new definition.
$definition_manager->installFieldStorageDefinition('status_field', $bundle_of, $bundle_of, $new_status_field);

// Restore the values.
$value_map = [
  '1' => 'yes',
  '0' => 'no',
];
foreach ($status_values as $id => $value) {
  $database->update($table_name)
    ->fields(['status_field' => $value_map[$value]])
    ->condition($id_key, $id)
    ->execute();
}

// Commit transaction.
unset($transaction);

Deleting a base field type

If you need to delete a custom field from a BaseFieldDefinition the general process is:

  1. Delete the code from the EntityClass::BaseFieldDefinition
  2. Delete the field from entity_keys in entity definition notation if it is defined there
  3. Uninstall the field
  4. Run cron

For example:

  $update_manager = \Drupal::entityDefinitionUpdateManager();
  $definition = $update_manager->getFieldStorageDefinition('name_of_old_field_to_delete', 'entity_type');
  $update_manager->uninstallFieldStorageDefinition($definition);

Note that while the field data will be gone from the database, the field widget may still show up on entity edit pages until you've run cron.

See the change record on Write update functions for entity schema updates, automation removed for other similar examples.

Useful drush commands while developing your hook_update_N

Checking the current schema version of a module

Drupal 8

drush php-eval "echo drupal_get_installed_schema_version('my_module');"

Drupal 9+

drush php-eval "echo \Drupal::service('update.update_hook_registry')->getInstalledVersion('my_module');"

Manually setting the current schema version of a module

Drupal 8

drush php-eval "echo drupal_set_installed_schema_version('my_module', '8000');"

Drupal 9+

drush php-eval "\Drupal::service('update.update_hook_registry')->setInstalledVersion('my_module', '9000');"

Tags

Help improve this page

Page status: Needs work

You can: