Change record status: 
Project: 
Introduced in branch: 
8.7.x
Introduced in version: 
8.7.0
Description: 

Drupal 8.0.0 introduced the concept of automatic entity updates, which, in principle, allows a site to update the existing schema of an entity type and its fields storage definitions to the latest (in-code) definitions with minimal effort, for example by using the drush entity-updates command.

However, in practice, this automatic update process has been proven to be dangerous because it can lead to unforeseen side effects and critical bugs when executed in the context of regular database updates on production environments, so core itself stopped using it before Drupal 8.0.0 was even released as stable.

Starting with 8.7.0, Drupal core no longer provides support for automatic entity updates. Whenever an entity type or field storage definition needs to be created, changed or deleted, it has to be done with an explicit update function as provided by the Update API, and using the API provided by the entity definition update manager.

The entity definition update manager no longer supports changes that would involve a table layout change through ::updateEntityType(). These now require the new Entity Update API (see examples below). This can also be used if there is existing data for the entity type or field definition that needs to be changed.

In practice this should have no impact over regular deployment workflows, the only actual change is that drush entup will not work anymore until it is reimplemented in contrib. The command will be removed from drush core and added back to a module depending on devel to make it clear that drush entup is a developer tool and should never be part of a production workflow.

Code examples

⚠️Special case for fieldable entity-types

See this separate change-notice

Installing a new entity type definition

/**
 * Implements hook_update_N().
 */
function example_update_8701() {
  \Drupal::entityDefinitionUpdateManager()->installEntityType(new ConfigEntityType([
    'id' => 'rest_resource_config',
    'label' => new TranslatableMarkup('REST resource configuration'),
    'config_prefix' => 'resource',
    'admin_permission' => 'administer rest resources',
    'label_callback' => 'getLabelFromPlugin',
    'entity_keys' => ['id' => 'id'],
    'config_export' => [
      'id',
      'plugin_id',
      'granularity',
      'configuration',
    ],
  ]));
}

Updating an existing entity type when the update does not involve schema changes

function example_update_8701() {
  $definition_update_manager = \Drupal::entityDefinitionUpdateManager();
  $entity_type = $definition_update_manager->getEntityType('comment');
  $keys = $entity_type->getKeys();
  $keys['published'] = 'status';
  $entity_type->set('entity_keys', $keys);
  $definition_update_manager->updateEntityType($entity_type);
}

Updating an existing entity type when the update requires schema changes (in this case, converting an entity type to be revisionable)
Note that a post update function is being used here because events are fired while changing the entity type revisionable and translatable properties.

/**
 * Implements hook_post_update_NAME().
 */
function taxonomy_post_update_make_taxonomy_term_revisionable(&$sandbox) {
  $definition_update_manager = \Drupal::entityDefinitionUpdateManager();
  /** @var \Drupal\Core\Entity\EntityLastInstalledSchemaRepositoryInterface $last_installed_schema_repository */
  $last_installed_schema_repository = \Drupal::service('entity.last_installed_schema.repository');

  $entity_type = $definition_update_manager->getEntityType('taxonomy_term');
  $field_storage_definitions = $last_installed_schema_repository->getLastInstalledFieldStorageDefinitions('taxonomy_term');

  // Update the entity type definition.
  $entity_keys = $entity_type->getKeys();
  $entity_keys['revision'] = 'revision_id';
  $entity_keys['revision_translation_affected'] = 'revision_translation_affected';
  $entity_type->set('entity_keys', $entity_keys);
  $entity_type->set('revision_table', 'taxonomy_term_revision');
  $entity_type->set('revision_data_table', 'taxonomy_term_field_revision');
  $revision_metadata_keys = [
    'revision_default' => 'revision_default',
    'revision_user' => 'revision_user',
    'revision_created' => 'revision_created',
    'revision_log_message' => 'revision_log_message',
  ];
  $entity_type->set('revision_metadata_keys', $revision_metadata_keys);

  // Update the field storage definitions and add the new ones required by a
  // revisionable entity type.
  $field_storage_definitions['langcode']->setRevisionable(TRUE);
  $field_storage_definitions['name']->setRevisionable(TRUE);
  $field_storage_definitions['description']->setRevisionable(TRUE);
  $field_storage_definitions['changed']->setRevisionable(TRUE);

  $field_storage_definitions['revision_id'] = BaseFieldDefinition::create('integer')
    ->setName('revision_id')
    ->setTargetEntityTypeId('taxonomy_term')
    ->setTargetBundle(NULL)
    ->setLabel(new TranslatableMarkup('Revision ID'))
    ->setReadOnly(TRUE)
    ->setSetting('unsigned', TRUE);

  $field_storage_definitions['revision_default'] = BaseFieldDefinition::create('boolean')
    ->setName('revision_default')
    ->setTargetEntityTypeId('taxonomy_term')
    ->setTargetBundle(NULL)
    ->setLabel(new TranslatableMarkup('Default revision'))
    ->setDescription(new TranslatableMarkup('A flag indicating whether this was a default revision when it was saved.'))
    ->setStorageRequired(TRUE)
    ->setInternal(TRUE)
    ->setTranslatable(FALSE)
    ->setRevisionable(TRUE);

  $field_storage_definitions['revision_translation_affected'] = BaseFieldDefinition::create('boolean')
    ->setName('revision_translation_affected')
    ->setTargetEntityTypeId('taxonomy_term')
    ->setTargetBundle(NULL)
    ->setLabel(new TranslatableMarkup('Revision translation affected'))
    ->setDescription(new TranslatableMarkup('Indicates if the last edit of a translation belongs to current revision.'))
    ->setReadOnly(TRUE)
    ->setRevisionable(TRUE)
    ->setTranslatable(TRUE);

  $field_storage_definitions['revision_created'] = BaseFieldDefinition::create('created')
    ->setName('revision_created')
    ->setTargetEntityTypeId('taxonomy_term')
    ->setTargetBundle(NULL)
    ->setLabel(new TranslatableMarkup('Revision create time'))
    ->setDescription(new TranslatableMarkup('The time that the current revision was created.'))
    ->setRevisionable(TRUE);
  $field_storage_definitions['revision_user'] = BaseFieldDefinition::create('entity_reference')
    ->setName('revision_user')
    ->setTargetEntityTypeId('taxonomy_term')
    ->setTargetBundle(NULL)
    ->setLabel(new TranslatableMarkup('Revision user'))
    ->setDescription(new TranslatableMarkup('The user ID of the author of the current revision.'))
    ->setSetting('target_type', 'user')
    ->setRevisionable(TRUE);
  $field_storage_definitions['revision_log_message'] = BaseFieldDefinition::create('string_long')
    ->setName('revision_log_message')
    ->setTargetEntityTypeId('taxonomy_term')
    ->setTargetBundle(NULL)
    ->setLabel(new TranslatableMarkup('Revision log message'))
    ->setDescription(new TranslatableMarkup('Briefly describe the changes you have made.'))
    ->setRevisionable(TRUE)
    ->setDefaultValue('');

  $definition_update_manager->updateFieldableEntityType($entity_type, $field_storage_definitions, $sandbox);

  return t('Taxonomy terms have been converted to be revisionable.');
}

Uninstalling an entity type

function example_update_8701() {
  $entity_update_manager = \Drupal::entityDefinitionUpdateManager();
  $entity_type = $entity_update_manager->getEntityType('entity_type_id');
  $entity_update_manager->uninstallEntityType($entity_type);
}

Installing a new field storage definition

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

Updating an existing field storage definition

function example_update_8701() {
  $entity_definition_update_manager = \Drupal::entityDefinitionUpdateManager();
  $field_storage_definition = $entity_definition_update_manager->getFieldStorageDefinition('hostname', 'comment');
  $field_storage_definition->setDefaultValueCallback(Comment::class . '::getDefaultHostname');
  $entity_definition_update_manager->updateFieldStorageDefinition($field_storage_definition);
}

Uninstalling an existing field storage definition

function example_update_8701() {
  $definition_update_manager = \Drupal::entityDefinitionUpdateManager();
  if ($content_translation_status = $definition_update_manager->getFieldStorageDefinition('content_translation_status', 'taxonomy_term')) {
    $definition_update_manager->uninstallFieldStorageDefinition($content_translation_status);
  }
}
Impacts: 
Site builders, administrators, editors
Module developers
Distribution developers
Updates Done (doc team, etc.)
Online documentation: 
Not done
Theming guide: 
Not done
Module developer documentation: 
Not done
Examples project: 
Not done
Coder Review: 
Not done
Coder Upgrade: 
Not done
Other: 
Other updates done

Comments

plach’s picture

Automatic entity updates are being restored as a developer-only tool via https://www.drupal.org/project/devel_entity_updates.

edmargomes’s picture

This code doesn't work for me

/**
 * Implements hook_update_N().
 */
function example_update_8701() {
  \Drupal::entityDefinitionUpdateManager()->installEntityType(new ConfigEntityType([
    'id' => 'rest_resource_config',
    'label' => new TranslatableMarkup('REST resource configuration'),
    'config_prefix' => 'resource',
    'admin_permission' => 'administer rest resources',
    'label_callback' => 'getLabelFromPlugin',
    'entity_keys' => ['id' => 'id'],
    'config_export' => [
      'id',
      'plugin_id',
      'granularity',
      'configuration',
    ],
  ]));
}

I using Drupal 8.7 and need change Config to Content class

\Drupal::entityDefinitionUpdateManager()->installEntityType(
    new ContentEntityType([
      'id' => "custom_dashboard",
      'label' => new \Drupal\Core\StringTranslation\TranslatableMarkup("Custom Dashboard"),
      'handlers' => [
        "view_builder" => "Drupal\Core\Entity\EntityViewBuilder",
        "views_data" => "Drupal\riskregister\Entity\CustomDashboardViewsData",
    ],
    'base_table' => "custom_dashboard",
    'admin_permission' => "administer Custom Dashboard entities",
    'entity_keys' => [
      "id" => "id",
      "label" => "name",
      "uuid" => "uuid",
      "uid" => "user_id",
      "langcode" => "langcode",
      "status" => "status",
    ],
    'field_ui_base_route' => "custom_dashboard.settings"
    ])
  );
estoyausente’s picture

I'm trying to follow this guide to update a field setting in a decimal baseField in a custom entity but it doesn't work.

This is the update:

function bb_data_layer_update_8009() {
  $entity_definition_update_manager = \Drupal::entityDefinitionUpdateManager();
  $field_storage_definition = $entity_definition_update_manager->getFieldStorageDefinition('volume', 'bb_product');
  $field_storage_definition->setSettings(["scale" => 3, "precision" => 5]);
  $entity_definition_update_manager->updateFieldStorageDefinition($field_storage_definition);
}

And this is the baseField definition in the entity:

    $fields['volume'] = BaseFieldDefinition::create('decimal')
      ->setLabel(t('Bottle Volume'))
      ->setReadOnly(TRUE)
      ->setDescription(t('e.g. 0.75'))
      ->setSettings(["scale" => 3, "precision" => 5])
      ->setDisplayOptions('view', [
        'label' => 'above',
        'type' => 'decimal',
        'weight' => -5,
      ])
      ->setDisplayConfigurable('view', TRUE)
      ->setDisplayConfigurable('form', FALSE)
      ->setTranslatable(FALSE);

The original values for scale and precision were 2 and 10, I execute the update and it doesn't work. I debugged it but I don't find any error, exception or similar, just it doesn't work.

olexyy.mails@gmail.com’s picture

See my comment in. https://drupal.stackexchange.com/questions/230793/how-can-i-update-the-l...
This is about length property , but suppose relates to all:

In latest version of Drupal seems to work only reinstall option, so basic flow is:

$entityUpdateManager = \Drupal::entityDefinitionUpdateManager();
  $field_definition = $entityUpdateManager->getFieldStorageDefinition($fieldName, $entityTypeId);
  $entityUpdateManager->uninstallFieldStorageDefinition($field_definition);
  $newStorageDefinition = BaseFieldDefinition::create('string')
    ->setLabel(t('Label'))
    ->setSetting('max_length', 191)
    ->setDescription(t('Description.'))
    ->setDisplayConfigurable('form', TRUE)
    ->setDisplayConfigurable('view', TRUE); // Same as in entity type class.
  $entityUpdateManager->installFieldStorageDefinition(
    $fieldName, $entityTypeId, $moduleName, $newStorageDefinition
  );

Worked for me for updating field length along with index.

flocondetoile’s picture

/**
 * Make the my_entity entity type translatable.
 */
function my_module_post_update_1(&$sandbox) {
  // Here we update the entity type.
  $definition_update_manager = \Drupal::entityDefinitionUpdateManager();
  $entity_type = $definition_update_manager->getEntityType('my_entity');
  $entity_type->set('translatable', TRUE);
  $entity_type->set('data_table', 'my_entity_field_data');

  // We need to update the field storage definitions, for the langcode field, and for all
  // the fields we updated on the entity Class. Add here all the fields you updated in the
  // entity Class by adding setTranslatable(TRUE).
  /** @var \Drupal\Core\Entity\EntityLastInstalledSchemaRepositoryInterface $last_installed_schema_repository */
  $last_installed_schema_repository = \Drupal::service('entity.last_installed_schema.repository');
  $field_storage_definitions = $last_installed_schema_repository->getLastInstalledFieldStorageDefinitions('my_entity');
  $field_storage_definitions['title']->setTranslatable(TRUE);
  $field_storage_definitions['langcode']->setTranslatable(TRUE);

  // We need to add a new field, default langcode.
  $storage_definition = BaseFieldDefinition::create('boolean')
    ->setName('default_langcode')
    ->setLabel(t('Default translation'))
    ->setDescription(t('A flag indicating whether this is the default translation.'))
    ->setTargetEntityTypeId('my_entity')
    ->setTargetBundle(NULL)
    ->setTranslatable(TRUE)
    ->setRevisionable(TRUE)
    ->setDefaultValue(TRUE);
  $field_storage_definitions['default_langcode'] = $storage_definition;

  // And now we can launch the process for updating the entity type and the database 
  // schema, including data migration.
  $definition_update_manager->updateFieldableEntityType($entity_type, $field_storage_definitions, $sandbox);
}
fisherman90’s picture

Here is an example if you want to convert an existing, revisionable entity type to be translatable. Since it is revisionable we also need to add the 'revision_translation_affected' column to the table.

/**
 * Update hook for revisionable entities that were not translatable before.
 * Replace 'MYMODULE' with your module's machine name.
 * Replace 'ENTITYTYPE' with the machine name of your entity you'd like to update.
 * Put this into your MYMODULE.post_update.php file inside of your module's directory.
 */
function MYMODULE_post_update_make_ENTITYTYPE_translatable(&$sandbox) {

  $entity_type_machine_name = 'ENTITYTYPE';

  $definition_update_manager = \Drupal::entityDefinitionUpdateManager();
  /** @var \Drupal\Core\Entity\EntityLastInstalledSchemaRepositoryInterface $last_installed_schema_repository */
  $last_installed_schema_repository = \Drupal::service('entity.last_installed_schema.repository');

  $entity_type = $definition_update_manager->getEntityType($entity_type_machine_name);
  $field_storage_definitions = $last_installed_schema_repository
    ->getLastInstalledFieldStorageDefinitions($entity_type_machine_name);

  // Update the entity type definition.
  $entity_keys = $entity_type->getKeys();
  $entity_keys['langcode'] = 'langcode';
  $entity_keys['revision_translation_affected'] = 'revision_translation_affected';
  $entity_type->set('entity_keys', $entity_keys);

  $entity_type->setHandlerClass('translation', 'Drupal\content_translation\ContentTranslationHandler');

  $entity_type->set('translatable', TRUE);
  $entity_type->set('data_table', $entity_type_machine_name . '_field_data');

  $field_storage_definitions['langcode'] = BaseFieldDefinition::create('language')
    ->setName('langcode')
    ->setStorageRequired(TRUE)
    ->setTargetEntityTypeId($entity_type_machine_name)
    ->setLabel(t('Language code'))
    ->setDescription(t('The language code.'));
  $field_storage_definitions['default_langcode'] = BaseFieldDefinition::create('boolean')
    ->setName('default_langcode')
    ->setTargetEntityTypeId($entity_type_machine_name)
    ->setTargetBundle(NULL)
    ->setLabel(t('Default Language code'))
    ->setStorageRequired(TRUE)
    ->setTargetEntityTypeId($entity_type_machine_name)
    ->setReadOnly(TRUE)
    ->setRevisionable(TRUE)
    ->setDescription(t('Indicates if this is the default language.'));
  $field_storage_definitions['revision_translation_affected'] = BaseFieldDefinition::create('boolean')
    ->setName('revision_translation_affected')
    ->setTargetEntityTypeId($entity_type_machine_name)
    ->setTargetBundle(NULL)
    ->setLabel(new TranslatableMarkup('Revision translation affected'))
    ->setDescription(new TranslatableMarkup('Indicates if the last edit of a translation belongs to current revision.'))
    ->setReadOnly(TRUE)
    ->setRevisionable(TRUE)
    ->setTranslatable(TRUE);

  $definition_update_manager->updateFieldableEntityType($entity_type, $field_storage_definitions, $sandbox);

  return t($entity_type_machine_name . ' entities have been converted to be translatable.');
}

Also beware when using Views: Because making an entity translatable changes the schema and the base_table, all views depending on your entity's base_table need to be updated to have the new one:

/**
 * Update the base table from ENTITYTYPE to ENTITYTYPE_field_data in views.
 * Replace 'MYMODULE' with your module's machine name.
 * Replace 'ENTITYTYPE' with the machine name of your entity you'd like to update.
 * Put this into your MYMODULE.post_update.php file inside of your module's directory.
 */
function MYMODULE_post_update_ENTITYTYPE_views(&$sandbox = NULL) {

  $entity_type_machine_name = 'ENTITYTYPE';

  \Drupal::classResolver(ConfigEntityUpdater::class)->update($sandbox, 'view', function ($view) {
    /** @var \Drupal\views\ViewEntityInterface $view */
    if ($view->get('base_table') == $entity_type_machine_name) {
      $view->set('base_table', $entity_type_machine_name . '_field_data');
      return TRUE;
    }
    return FALSE;
  });
}
cosmicdreams’s picture

I read through the comments here and didn't find one that helped me when I needed to create an update hook that would properly created a new entity. I'll try to provide the code sample here without including client specific details.

function MODULE_NAME_update_88###() {
  $definition_update_manager = Drupal::entityDefinitionUpdateManager();

  // Basically the entity plugin definition for the custom entity, converted to an associative array.
  $entity_definition = new ContentEntityType([
    "id" => "my_custom_entity_entity",
    "label" => new TranslatableMarkup("My Custom Entity"),
    "handlers" => [
      "view_builder" => "Drupal\Core\Entity\EntityViewBuilder",
      "list_builder" => "Drupal\module_name\MyCustomEntityEntityListBuilder",
      "views_data" => "Drupal\module_name\Entity\MyCustomEntityEntityViewsData",

      "form" => [
        "default" => "Drupal\module_name\Form\MyCustomEntityEntityForm",
        "add" => "Drupal\module_name\Form\MyCustomEntityEntityForm",
        "edit" => "Drupal\module_name\Form\MyCustomEntityEntityForm",
        "delete" => "Drupal\module_name\Form\MyCustomEntityEntityDeleteForm",
      ],
      "route_provider" => [
        "html" => "Drupal\module_name\MyCustomEntityEntityHtmlRouteProvider",
      ],
      "access" => "Drupal\module_name\MyCustomEntityEntityAccessControlHandler",
    ],
    "base_table" => "my_custom_entity_entity",
    "translatable" => FALSE,
    "admin_permission" => "administer My Custom Entity entities",
    "entity_keys" => [
      "id" => "id",
      "label" => "label",
      "uuid" => "uuid",
      "published" => "status",
    ],
    "links" => [
      "canonical" => "/admin/structure/my_custom_entity_entity/{my_custom_entity_entity}",
      "add-form" => "/admin/structure/my_custom_entity_entity/add",
      "edit-form" => "/admin/structure/my_custom_entity_entity/{my_custom_entity_entity}/edit",
      "delete-form" => "/admin/structure/my_custom_entity_entity/{my_custom_entity_entity}/delete",
      "collection" => "/admin/structure/my_custom_entity_entity",
    ],
    "field_ui_base_route" => "my_custom_entity_entity.settings",
  ]);
  
  // Finally you need to manually set the class to where the entity plugin definition is.
  $entity_definition->setClass('Drupal\module_name\Entity\MyCustomEntityEntity');
     
   // Create new entity type.
  $definition_update_manager->installEntityType($entity_definition);
}
elgandoz’s picture

I love you

kkalaskar’s picture

clearCachedDefinitions is one step which save me from disaster.... custom entity broken after update 8.7.11
\Drupal::entityTypeManager()->clearCachedDefinitions();

$entity_type = \Drupal::entityTypeManager()->getDefinition('MY ENTITY ID');
\Drupal::entityDefinitionUpdateManager()->installEntityType($entity_type);

lexsoft00’s picture

I can confirm that the above worked for me for a custom fieldable entity on Drupal 8.8.1:
Installed devel_php(on path devel/php) and run the bellow.
Do not do this in a production environment without prior testing.

\Drupal::entityTypeManager()->clearCachedDefinitions();

$entity_type = \Drupal::entityTypeManager()->getDefinition('MY ENTITY ID');
\Drupal::entityDefinitionUpdateManager()->installEntityType($entity_type);
sharif.elshobkshy’s picture

I'm trying to create an attribute field to all nodes (like "Published" or "Generate automatic URL alias" fields).


use Drupal\Core\Field\BaseFieldDefinition;

function moduleName_install() {
  $storage = BaseFieldDefinition::create('boolean')
    ->setLabel(t('Sample checkbox'))
    ->setDescription(t('This is a test checkbox to add to nodes.'))
    ->setRevisionable(TRUE)
    ->setTranslatable(TRUE)
    ->setDisplayOptions('form', [
      'type' => 'boolean_checkbox',
      'settings' => [
        'display_label' => TRUE,
      ],
    ])
    ->setDisplayConfigurable('form', TRUE);

  \Drupal::entityDefinitionUpdateManager()
    ->installFieldStorageDefinition('sample_checkbox', 'node', 'node', $storage);

However, I'm not able to see the field in the node forms.
I'll appreciate help.

sharif.elshobkshy’s picture

The field was created, but I forgot (after 7 hours the brain works at half engine) to add the field to the form.

/**
 * Implements hook_entity_base_field_info().
 */
function moduleName_entity_base_field_info(EntityTypeInterface $entity_type) {
  $fields = [];
  if ($entity_type->id() === 'node') {
    $fields['lti_resource'] = BaseFieldDefinition::create('boolean')
      ->setLabel(t('Sample checkbox'))
      ->setDescription(t('This is a test checkbox to add to nodes.'))
      ->setRevisionable(TRUE)
      ->setTranslatable(TRUE)
      ->setDisplayOptions('form', [
        'type' => 'boolean_checkbox',
        'settings' => [
          'display_label' => TRUE,
        ],
      ])
      ->setDisplayConfigurable('form', TRUE);

    return $fields;
  }
}

Hope that helps for the next person with the same doubt.
Regards.

Jalite1991’s picture

so I attempted to create a patch for the config pages module to allow them to be revisionable, but It appears that this code is not working.

I can't find any new tables and the schema for the config pages hasn't changed.

  \Drupal::entityTypeManager()->clearCachedDefinitions();


  $definition_update_manager = \Drupal::entityDefinitionUpdateManager();
  /** @var \Drupal\Core\Entity\EntityLastInstalledSchemaRepositoryInterface $last_installed_schema_repository */
  $last_installed_schema_repository = \Drupal::service('entity.last_installed_schema.repository');

  $entity_type = $definition_update_manager->getEntityType('config_pages');
  $field_storage_definitions = $last_installed_schema_repository->getLastInstalledFieldStorageDefinitions('config_pages');

  // Update the entity type definition.
  $entity_keys = $entity_type->getKeys();
  $entity_keys['revision'] = 'revision_id';
  $entity_keys['revision_translation_affected'] = 'revision_translation_affected';
  $entity_type->set('entity_keys', $entity_keys);
  $entity_type->set('revision_table', 'config_pages_revision');
  $entity_type->set('revision_data_table', 'config_pages_field_revision');
  $revision_metadata_keys = [
    'revision_default' => 'revision_default',
    'revision_user' => 'revision_user',
    'revision_created' => 'revision_created',
    'revision_log_message' => 'revision_log_message',
  ];
  $entity_type->set('revision_metadata_keys', $revision_metadata_keys);

  // Update the field storage definitions and add the new ones required by a
  // revisionable entity type.

  $field_storage_definitions['changed']->setRevisionable(TRUE);
  $field_storage_definitions['label']->setRevisionable(TRUE);
  $field_storage_definitions['context']->setRevisionable(TRUE);


  $field_storage_definitions['revision_id'] = BaseFieldDefinition::create('integer')
    ->setName('revision_id')
    ->setTargetEntityTypeId('config_pages')
    ->setTargetBundle(NULL)
    ->setLabel(new TranslatableMarkup('Revision ID'))
    ->setReadOnly(TRUE)
    ->setSetting('unsigned', TRUE);

  $field_storage_definitions['revision_default'] = BaseFieldDefinition::create('boolean')
    ->setName('revision_default')
    ->setTargetEntityTypeId('config_pages')
    ->setTargetBundle(NULL)
    ->setLabel(new TranslatableMarkup('Default revision'))
    ->setDescription(new TranslatableMarkup('A flag indicating whether this was a default revision when it was saved.'))
    ->setStorageRequired(TRUE)
    ->setInternal(TRUE)
    ->setTranslatable(FALSE)
    ->setRevisionable(TRUE);

  $field_storage_definitions['revision_translation_affected'] = BaseFieldDefinition::create('boolean')
    ->setName('revision_translation_affected')
    ->setTargetEntityTypeId('config_pages')
    ->setTargetBundle(NULL)
    ->setLabel(new TranslatableMarkup('Revision translation affected'))
    ->setDescription(new TranslatableMarkup('Indicates if the last edit of a translation belongs to current revision.'))
    ->setReadOnly(TRUE)
    ->setRevisionable(TRUE)
    ->setTranslatable(TRUE);

  $field_storage_definitions['revision_created'] = BaseFieldDefinition::create('created')
    ->setName('revision_created')
    ->setTargetEntityTypeId('config_pages')
    ->setTargetBundle(NULL)
    ->setLabel(new TranslatableMarkup('Revision create time'))
    ->setDescription(new TranslatableMarkup('The time that the current revision was created.'))
    ->setRevisionable(TRUE);
  $field_storage_definitions['revision_user'] = BaseFieldDefinition::create('entity_reference')
    ->setName('revision_user')
    ->setTargetEntityTypeId('config_pages')
    ->setTargetBundle(NULL)
    ->setLabel(new TranslatableMarkup('Revision user'))
    ->setDescription(new TranslatableMarkup('The user ID of the author of the current revision.'))
    ->setSetting('target_type', 'user')
    ->setRevisionable(TRUE);
  $field_storage_definitions['revision_log_message'] = BaseFieldDefinition::create('string_long')
    ->setName('revision_log_message')
    ->setTargetEntityTypeId('config_pages')
    ->setTargetBundle(NULL)
    ->setLabel(new TranslatableMarkup('Revision log message'))
    ->setDescription(new TranslatableMarkup('Briefly describe the changes you have made.'))
    ->setRevisionable(TRUE)
    ->setDefaultValue('');

  $definition_update_manager->updateFieldableEntityType($entity_type, $field_storage_definitions, $sandbox);

  return t('Config Pages terms have been converted to be revisionable.');

I am having a similar issue to https://www.drupal.org/project/drupal/issues/3052464 where the error I recieve is
`The entity update process failed while processing the entity type config_pages, ID: 1. in Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema->copyData() (line 220 of /app/web/core/lib/Drupal/Core/Entity/Sql/SqlFieldableEntityTypeListenerTrait.php).`

tjtj’s picture

I get "The SQL storage cannot change the schema for an existing field (fid in file entity) with data." for a dozen or so different entities. Where do I put the code to fix this?

linebreak’s picture

The example code for "Installing a new field storage definition" doesn't seem to work for computed fields that don't store anything in the database.
Any clue to find documentation in order to declare a new computed field in an update function ? Just adding it to hook_entity_base_info and clearing cache is not sufficient because this hook is only called when the module is installed (well, that's my understanding at this point).
(core 8.8.6)

amateescu’s picture

@linebreak, computed fields don't have to be installed through the entity definition update manager, so declaring them in a hook_entity_base_field_info() implementation is enough. That hook is invoked when the field storage definition caches are empty, so you just need to clear the caches and the new field should be registered.

linebreak’s picture

Thanks a lot for your answer, I'll focus my investigations on why updating that hook doesn't work in my case then.

Mohamed.Osama’s picture

/**
 * Implements hook_update_N().
 */
function MYMODULE_update_8705(&$sandbox) {
  $type_manager = \Drupal::entityTypeManager();
  $type_manager->clearCachedDefinitions();
  $entity_type = $type_manager->getDefinition('my_entity');
  \Drupal::entityDefinitionUpdateManager()->installEntityType($entity_type);

  return t('Installed the MY ENTITY entity type');
}
darienmh’s picture

This snippet works fine in drupal 8.9 and 9.1.

/**
 * Implements hook_update_N().
 */
function CUSTOM_MODULE_update_8001() {

  $entity_type_manager = \Drupal::entityTypeManager();
  $entity_type_manager->clearCachedDefinitions();
  $entity_type = $entity_type_manager->getDefinition('custom_entity');
  \Drupal::entityDefinitionUpdateManager()->installEntityType($entity_type);

  drupal_flush_all_caches();

  return t('Installed the CUSTOM ENTITY entity type');
}
yas’s picture

@darienmh

Thank you for sharing your solution. It worked for me, and I improved your snippet without putting a specific entity type ID.

/**
 * Implements hook_update_N().
 */
function CUSTOM_MODULE_update_8001() {

  $entity_type_manager = \Drupal::entityTypeManager();
  $entity_type_manager->clearCachedDefinitions();

  $entity_type_ids = [];
  $change_summary = \Drual::service('entity.definition_update_manager')->getChangeSummary();
  foreach ($change_summary as $entity_type_id => $change_list) {
    $entity_type = $enty_type_manager->getDefinition($entity_type_id);
    \Drupal::entityDefinitionUpdateManager()->installEntityType($entity_type);
    $entity_type_ids[] = $entity_type_id;
  }
  drupal_flush_all_caches();

  return t("Installed/Updated the entity type(s): @entity_type_ids", [
    '@entity_type_ids' => implode(', ', $entity_type_ids),
  ]);
}

See also: https://www.drupal.org/project/cloud/issues/3198478

acidaniel’s picture

This solution is perfect, you need to fix some typos though, but however is working perfect!, thanks @yas ++

Mohamed.Osama’s picture

Thanks so much, working fine, but need to edit some typo, here after updates

/**
 * Implements hook_update_N().
 */
function HOOK_update_8701() {

  $entity_type_manager = \Drupal::entityTypeManager();
  $entity_type_manager->clearCachedDefinitions();

  $entity_type_ids = [];
  $change_summary = \Drupal::service('entity.definition_update_manager')->getChangeSummary();
  foreach ($change_summary as $entity_type_id => $change_list) {
    $entity_type = $entity_type_manager->getDefinition($entity_type_id);
    \Drupal::entityDefinitionUpdateManager()->installEntityType($entity_type);
    $entity_type_ids[] = $entity_type_id;
  }
 

  return t("Installed/Updated the entity type(s): @entity_type_ids", [
    '@entity_type_ids' => implode(', ', $entity_type_ids),
  ]);
}
mcortes19’s picture

This solution works in D 9.1.13, thanks so much!

SunnyGambino’s picture

Hey, thanks for sharing this! Life saver! :)
I had an error like

SQLSTATE[42S02]: Base table or view not found: 1146 Table 'db.node__field_s  
  ection_6_image_alignment' doesn't exist: UPDATE "node__field_section_6_imag  
  e_alignment" SET "deleted"=:db_update_placeholder_0; Array                   
  (                                                                            
      [:db_update_placeholder_0] => 1                                          
  )         

Even if I used node_entity_update module or any hint/trick from the community, it failed. This was the real solution! Thanks again! :)

msypes’s picture

Excellent! Worked like a charm (v9.5.3)

Michael

robert_t_taylor’s picture

This solved a problem I have been working at for quite some time, @yas! Arigato gozaimasu!

akhoury’s picture

This should not be used when there are changes to Database, while it removes the error in status report but it does not actually perform any of the DB updates

Eduardo Morales Alberti’s picture

First define the field on your custom content entity

 /**
   * {@inheritdoc}
   */
  public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
...
    $fields['your_field'] = BaseFieldDefinition::create('entity_reference')
      ->setSetting('target_type', 'node')
      ->setLabel(t('Destination reference'))
      ->setDescription(t('Destination reference'));

Then install it using an update on your your_module.install file

function your_module_update_80XX() {

  $module_name = 'your_module';
  $entity_type = 'your_entity';
  $fields = [
    'your_field',
  ];

  $entity_definition_update_manager = \Drupal::entityDefinitionUpdateManager();
  $field_definitions = \Drupal::service('entity_field.manager')
    ->getFieldDefinitions($entity_type, $entity_type);
  foreach ($fields as $field_name) {
    if (!empty($field_definitions[$field_name]) && $field_definitions[$field_name] instanceof FieldStorageDefinitionInterface) {
      $entity_definition_update_manager
        ->installFieldStorageDefinition(
          $field_name,
          $entity_type,
          $module_name,
          $field_definitions[$field_name]);
    }
  }

}
Gonzalo2683’s picture

Good contribution @eduardo-morales-alberti, I would only add clearing the definition cache before making the changes.
\Drupal::entityTypeManager()->clearCachedDefinitions();

gbyte’s picture

@eduardo-morales-albert If you want to install fields like 'created', you may need to drop the check for instanceof FieldStorageDefinitionInterface like so:

function your_module_update_80XX() {

  $module_name = 'your_module';
  $entity_type = 'your_entity';
  $fields = [
    'your_field',
  ];

  $entity_definition_update_manager = \Drupal::entityDefinitionUpdateManager();
  $field_definitions = \Drupal::service('entity_field.manager')
    ->getFieldDefinitions($entity_type, $entity_type);
  foreach ($fields as $field_name) {
    if (!empty($field_definitions[$field_name])) {
      $entity_definition_update_manager
        ->installFieldStorageDefinition(
          $field_name,
          $entity_type,
          $module_name,
          $field_definitions[$field_name]);
    }
  }

}

michal.k’s picture

Hi,

I'm trying Entity API. Have created Property entity type with some fields. I forgot to set image and body fields to be revisionable. Now I have some data in production database and wanted to make these fields revisionable from now on. I have updated the defionition of these fields setting them revisionable in PropertyEntity.php and tried following code to update database schema:

function mymodule_update_9001 {
  $manager = \Drupal::entityDefinitionUpdateManager();
  // Body field.
  $body_storage_definition = $manager->getFieldStorageDefinition('body', 'property_entity');
  $body_storage_definition->setRevisionable(TRUE);
  $manager->updateFieldStorageDefinition($body_storage_definition);
  // Image field.
  $image_storage_definition = $manager->getFieldStorageDefinition('image', 'property_entity');
  $image_storage_definition->setRevisionable(TRUE);
  $manager->updateFieldStorageDefinition($image_storage_definition);
}

The code above works fine when there is no data in database. However once Properties are in database the code generates a following error:

Exception thrown while performing a schema update. SQLSTATE[42000]: Syntax error or access violation: 1072 Key column 'body__format' doesn't exist in table: ALTER TABLE "property_entity_field_revision" ADD INDEX `property_entity_field__body__format` (`body__format`); Array ( )

I was wondering if there is a quick trick to fix this, because only thing that comes to my mind is to do the following:
1. Get the data from database.
2. Truncate the tables.
3. Run the code above to update database schema on empty tables.
4. Fill updated tables with the data previously retrieved.

Thanks in advance for any suggestions.
Michał.