11

I have a content type with a field_maps whose type is Text (plain, long). I needed to change it to Text (plain).

So I've done this SQL statement:

ALTER TABLE node__field_maps MODIFY field_maps_value VARCHAR(200);

It seems that field_type has changed to VARCHAR(200), but there is still one more problem: On the content type and manage fields tabs, the field is still shown as Text (plain, long).

How do I change it to Text (plain)?

3
  • Do you have production data in the database? Because the easiest way is to delete the old field and install it as you like.
    – Eyal
    Commented Jul 31, 2016 at 12:24
  • 1
    @Hefi you can try my answer to a similar Q. You can always put your site in maintenance mode for a few secs/mins while you do it. Do a dry run on a test/dev server first.
    – No Sssweat
    Commented Jul 31, 2016 at 13:09
  • 2
    You should write an update hook to perform this task. Basically you will need to copy the data to a variable, delete the field install the new field and insert the data back. (All in the same update function)
    – Eyal
    Commented Aug 1, 2016 at 6:57

4 Answers 4

21

I have also get this kind of issue that change field type from one to another with existing data. So as @Eyal mentioned, we can write an update hook to

  1. export data from a field table to a variable
  2. remove fields of all bundles
  3. add new fields of all bundles
  4. restore data to field table

Here is the code example

use \Drupal\field\Entity\FieldStorageConfig;
use \Drupal\field\Entity\FieldConfig;

/**
 * Change node__field_maps from string_long to string type.
 */
function mymodule_update_8XXX() {
  $database = \Drupal::database();
  $table = 'node__field_maps';
  $entity_type = 'node';
  $field_name = 'field_maps';

  $field_storage = FieldStorageConfig::loadByName($entity_type, $field_name);

  if (is_null($field_storage)) {
    return;
  }

  $rows = NULL;

  if ($database->schema()->tableExists($table)) {
    // The table data to restore after the update is completed.
    $rows = $database->select($table, 'n')
      ->fields('n')
      ->execute()
      ->fetchAll();
  }

  $new_fields = array();

  // Use existing field config for new field.
  foreach ($field_storage->getBundles() as $bundle => $label) {
    $field = FieldConfig::loadByName($entity_type, $bundle, $field_name);
    $new_field = $field->toArray();
    $new_field['field_type'] = 'string';
    $new_field['settings'] = array();

    $new_fields[] = $new_field;
  }

  // Deleting field storage which will also delete bundles(fields).
  $new_field_storage = $field_storage->toArray();
  $new_field_storage['type'] = 'string';
  $new_field_storage['settings'] = array(
    'max_length' => 255,
    'is_ascii' => FALSE,
    'case_sensitive' => FALSE,
  );

  $field_storage->delete();

  // Purge field data now to allow new field and field_storage with same name
  // to be created. You may need to increase batch size.
  field_purge_batch(10);

  // Create new field storage.
  $new_field_storage = FieldStorageConfig::create($new_field_storage);
  $new_field_storage->save();

  // Create new fields.
  foreach ($new_fields as $new_field) {
    $new_field = FieldConfig::create($new_field);
    $new_field->save();
  }

  // Restore existing data in the same table.
  if (!is_null($rows)) {
    foreach ($rows as $row) {
      $database->insert($table)
        ->fields((array) $row)
        ->execute();
    }
  }

}

NOTE: Always backup DB and test code before running this code

Other thoughts In my case, we also have exported configures which means the field type and settings are also included in field_storage_config, field_config, entity_form_display, and entity_view_display config files. So we have to update config settings in those files as well. One easy way could be:

  • clone your website
  • delete the field and add a field with the same name again in field UI
  • export configures to use them in the repo
  • make sure the config uuid for the field and storage were not changed (change the uuid back if it was) to avoid the import potentially removing data.
  • import configure after DB update and clear cache again

This is code is inspired by @drugan in the post https://www.drupal.org/node/2816859

4
  • 2
    Great code - thanks!! I got this to work for a similar field change. Be aware, the new field will be disabled in the form display.. I didn't think it worked at first but then realized it was just disabled. Commented Feb 12, 2018 at 19:28
  • I have copy the content by doing copy table and rename field and table value . value now showing in the new fields and i have move content from plain text to number . Now i have set sorting according to number (integer) .its still showing according to plain text 10 than 2. whats the solution for this need your help? Commented Jun 30, 2018 at 8:02
  • 1
    While the update hook does indeed work, having tested in on several different fields, the suggestion handling of config will cause you to loose all data stored to said field, as the config import will see the configs as separate fields and delete the original with it's contents before adding the new one. To avoid this, once you export the config make sure the uuid of the config is NOT changed (basically change it back to the original field's uuid) that way the config system will assume it's the same field and just update it. I edited your answer to include this info. Commented Dec 10, 2019 at 10:45
  • One problem here is that dependent config gets damaged. E.g. the field is removed from view modes and form modes, and views and search indices using the field break.
    – donquixote
    Commented Jul 19, 2022 at 22:00
0

I was facing a similar issue. After running the update_hook described I was still facing a message about "Mismatched entity and/or field definitions" on status overview page. I think it was due to drush entup being removed in Drupal 8.7 I was able to solve this by manually forcing an update of FieldStorageDefinition like this:

function my_module_update_8002() {
  $entity_definition_update_manager = \Drupal::entityDefinitionUpdateManager();
  $entity_type = '[my_entity_type]';
  $field_name = '[my_field_name]';
  $field_storage = FieldStorageConfig::loadByName($entity_type, $field_name);
  $entity_definition_update_manager->updateFieldStorageDefinition($field_storage);
}
0

Druidfire is your friend. It's pure magic as the name suggests: it's a mini framework to manipulate entity data stored in the default SQL storage and the relevant configuration as well, bypassing most APIs. This particular conversion is not implemented yet but it wouldn't take more than ten minutes to add it, it's really easy. It already does string to formatted text for example with the string2formatted* methods, use these as a template for the change you want to implement.

-1

This is the most optimal solution i found. 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');
}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.