Change field type with existing data on Drupal 8

On this blog post, you will learn how to change a field type with existing data on Drupal.

Sometimes, you might be asked to perform this kind of changes on a content type, even if you have considered all use cases for the content types on the CMS or how the usage in the long term will be.

If you try to do this change from the admin pages located at:

Home > Administration > Structure > Content types > {Content type name} > Manage fields > {Field name}

You will probably get this error:

The field can’t be changed since already has data

One way to do this is by using a hook_update_N, on my example, the change will be from “Text (plain)” to “Text (formatted, long)” type. This is one of the usual cases when a client wants to add more than 255 characters on the text field.

function mymodule_update_8001() {
  $database = \Drupal::database();
  $entityType = 'node';
  $fieldName = 'field_article';
  $table = $entityType . '__' . $fieldName;
  $currentRows = NULL;
  $newFieldsList = [];
  $fieldStorage = FieldStorageConfig::loadByName($entityType, $fieldName);

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

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

  // Use existing field config for new field.
  foreach ($fieldStorage->getBundles() as $bundle => $label) {
    $field = FieldConfig::loadByName($entityType, $bundle, $fieldName);
    $newField = $field->toArray();
    $newField['field_type'] = 'text_long';
    $newField['settings'] = [];
    $newFieldsList[] = $newField;
  }

  // Deleting field storage which will also delete bundles(fields).
  $newFieldStorage = $fieldStorage->toArray();
  $newFieldStorage['type'] = 'text_long';
  $newFieldStorage['settings'] = [];

  $fieldStorage->delete();

  // Purge field data now to allow new field and field_storage with same name
  // to be created.
  field_purge_batch(40);

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

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

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

These are some steps to follow:

  1. Look for the machine name of the field.
  2. Go to `admin/reports/fields` and search for the field targeted to be changed. It’s important to know how many content types are using it.
  3. Look for the table on the DB used by the field and check how many rows there are.
  4. Add the snippet in a {module_name}.install from one of your custom modules.
  5. Replace the $entityType and $fieldName variables according to the entity and field  targeted on your case.
  6. Prepare a database backup.
  7. Run the update by going to /update.php on your browser or by running the command drush updb on your terminal.
  8. Check if the update was successfully performed.
  9. Use the information from steps 2) and 3) and check if the field content is still there.

If you are using Configuration Management feature on your site, here are some additional steps:

  1. After you verify that the new update works fine, export the new configuration by running drush config:export sync (on my case, “sync” is the name of the configuration directory)
  2. When you need to run this update on other environments, here is the order on how the commands will be executed:
    1. drush updb -y
    2. drush config:import sync
  3. Run: drush updb -y

You can find more info about updating entities and fields in Drupal 8 on this page of the official documentation.



10 thoughts on “Change field type with existing data on Drupal 8

  1. Hi Luis,

    This worked great. The only thing is that then you get a ENTITY/FIELD DEFINITIONS
    Mismatched entity and/or field definitions in the status report. I understand that `drush entity-updates` would normall solve this issue, but as of Drupal 8.7 this is deprecated. I just became aware that `devel entity updates` brings the command back, but the module explicitly indicates NOT to run in production. Amy guidance on how to create a hook for this particular fix? Thanks for your help.

  2. Great, work fine, just added some line in the module :
    use Drupal\field\Entity\FieldConfig;
    use Drupal\field\Entity\FieldStorageConfig;
    In my case converted decimal to float.

    I changed this line :
    $newField[‘field_type’] = ‘float’;
    and
    $newFieldStorage[‘type’] = ‘float’;

    Thank’s.

  3. I am following these steps
    I see a differences between my dev and prod environments
    For example, In the step no. 2 (.. /admin/reports/fields)

    in dev env, I see …
    Field Name : field_yield
    Used In : recipe (link to recipe fields editing form page)

    Where as in prod env, I see
    Field Name :field_yield
    Used In :

    All the user entered data is there in prod env. Still the above report is not listing the link to the fields page.

    What table could have gone wrong during migration from dev to prod?
    Any clue? How to restore such table?
    Please help.
    Thanks

  4. Thanks! This was a great snippet that worked for me converting from a float to a decimal. Really appreciate it.

    FWIW I wrapped the database insert in a try {} catch block because if there were any insert exceptions the entire process failed.

    In my case it failed because two rows had numbers too large and I wasn’t too concerned about losing the data, so I just caught the exception but didn’t halt the processing.

    try {
    $database->insert($table)
    ->fields((array) $row)
    ->execute();
    } catch (Exception $e) {
    var_dump($e->getMessage());
    }

  5. I tried but I am getting Null when using FieldStorageConfig::loadByName and it return. I checked i have field existing in my system

  6. Thank you very much for this very helpful blog entry. As it’s three years old now, these links may help if you run into similar cases:
    https://www.drupal.org/project/drupal/issues/937442
    https://www.drupal.org/docs/drupal-apis/update-api/updating-entities-and-fields-in-drupal-8#s-updating-field-storage-config-items

    I also wrote a blog entry on this (still complicated) topic, that may help here:
    https://julian.pustkuchen.com/en/programmatically-change-field-type-drupal-8

Leave a Reply to Hawkeye Tenderwolf Cancel reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.