How to modify a field with existing data in Drupal?

| | 3 min read
There is data for this field in the database. The field settings cano no longer be changed.
There is data for this field in the database. The field settings can no longer be changed.

As a Drupal site builder or developer, you might have encountered situations where you want to change the type of the field in a content type. If there is existing data in the field, this will not be possible with the Drupal backend interface. The proper way to solve this is to use update_hook in your module.

Change field type with existing data on Drupal

The following example shows how to change the field from “Text (plain)” to “Text (formatted, long)” type

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

We also need to follow the below steps

  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 in 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 the Configuration Management feature on your site, There are some additional steps:

  1. After you verify that the new update works fine, export the new configuration by running Drush config: export sync 
  2. When you need to run this update on other environments, here is the order in how the commands will be executed
    1. drush updb -y
    2. drush config:import sync
    3. Run: drush updb -y