Drupal Tip: How to change field length with existing data

Every Drupal developer eventually hits this wall: an editor requests that a standard text field (like a headline or subheadline) be expanded from 255 to 512 characters. You change the value in the field configuration YAML or try to update it programmatically via the Entity API, only to be hit with a fatal exception.

Drupal’s entity storage layer is intentionally defensive. If a field contains data, the Entity API refuses to modify its underlying database schema to prevent accidental data loss. This post walks through how to safely bypass this restriction, modify the database tables directly, and resync Drupal’s internal schema state without losing a single row of production data.

"Changing a field size when data is present throws a FieldStorageDefinitionUpdateForbiddenException. The system prevents any schema updates via the standard API to guarantee data integrity, requiring manual schema manipulation."

To solve this cleanly, we must bypass the high-level Entity API and interact with the state engine at a lower level.

The Problems with the Standard API Approach:

  • Hard Block on Updates: SqlContentEntityStorageSchema::onFieldStorageDefinitionUpdate() triggers an immediate exception if data exists in the table.
  • Configuration is out-of-sync: Merely changing the field configuration .yml file and importing it via Drush will fail during config synchronization because the active database schema does not match.
  • Mismatched status reports: If you force configuration values, Drupal’s status report (/admin/reports/status) flags an entity schema mismatch, which can break hook implementations and automated tests.

Bypassing with a Database-First Approach: A Better Solution

  • Safe Widening: Altering a column from varchar(255) to varchar(512) is a non-destructive database operation. MySQL/MariaDB and PostgreSQL handle this without truncating or locking rows.
  • Manual Definition Alignment: By updating the low-level entity.storage_schema.sql key-value collection, we convince the Entity Definition Update Manager that the state is consistent.
  • Direct Config Manipulation: Overriding the field storage config directly through the raw configuration factory bypasses validation rules while updating runtime constraints.

How do we implement this approach?

We implement this change within a hook_post_update_NAME() hook inside your module's .post_update.php file. Post-update hooks run after regular hook_update_N hooks, ensuring the service container is fully available. In our example, we want to increase the field length of field_subheadline inside a paragraph type.

Place this logic in your custom module under the following structure:

web/modules/custom/my_module/
├── my_module.info.yml
├── my_module.module
└── my_module.post_update.php

 1<?php
 2
 3/**
 4 * @file
 5 * Post update functions for my_module module.
 6 */
 7
 8declare(strict_types=1);
 9
10use Drupal\Core\Database\Database;
11use Drupal\field\FieldStorageConfigInterface;
12
13/**
14 * Increase the maximum length of paragraph subheadline field to 512 characters.
15 */
16function my_module_post_update_increase_max_subheadline_length(array &$sandbox): void {
17  $entity_type = 'paragraph';
18  $field_name = 'field_subheadline_text';
19  $new_length = 512;
20  $value_column = "{$field_name}_value";
21  $tables = [
22    "{$entity_type}__{$field_name}",
23    "{$entity_type}_revision__{$field_name}",
24  ];
25
26  // 1. Alter the DB columns directly. Drupal's entity API refuses to change a
27  // field's schema once it holds data
28  // (SqlContentEntityStorageSchema::onFieldStorageDefinitionUpdate() throws
29  // FieldStorageDefinitionUpdateForbiddenException), so we widen the columns
30  // ourselves and then sync the tracked definitions below. Widening a varchar
31  // never truncates existing values.
32  $db_schema = Database::getConnection()->schema();
33  $column_spec = ['type' => 'varchar', 'length' => $new_length, 'not null' => FALSE];
34  foreach ($tables as $table) {
35    if ($db_schema->tableExists($table)) {
36      $db_schema->changeField($table, $value_column, $value_column, $column_spec);
37    }
38  }
39
40  // 2. Sync the installed SQL storage schema so the entity definition update
41  // manager stops reporting a mismatch. This is stored in the
42  // "entity.storage_schema.sql" key-value collection under the key
43  // "{entity_type}.field_schema_data.{field_name}" (see
44  // SqlContentEntityStorageSchema::loadFieldSchemaData()).
45  $kv = \Drupal::keyValue('entity.storage_schema.sql');
46  $schema_key = "{$entity_type}.field_schema_data.{$field_name}";
47  $field_schema = $kv->get($schema_key);
48  if (is_array($field_schema)) {
49    foreach ($field_schema as &$table_schema) {
50      if (is_array($table_schema)
51        && is_array($table_schema['fields'] ?? NULL)
52        && is_array($table_schema['fields'][$value_column] ?? NULL)
53        && array_key_exists('length', $table_schema['fields'][$value_column])
54      ) {
55        $table_schema['fields'][$value_column]['length'] = $new_length;
56      }
57    }
58    unset($table_schema);
59    $kv->set($schema_key, $field_schema);
60  }
61
62  // 3. Update the active field storage config. Use the raw config factory to
63  // bypass the entity API (a FieldStorageConfig::save() would re-trigger the
64  // forbidden-schema-change check from step 1).
65  \Drupal::configFactory()
66    ->getEditable("field.storage.{$entity_type}.{$field_name}")
67    ->set('settings.max_length', $new_length)
68    ->save();
69
70  // 4. Update the last-installed field storage definition so it matches the
71  // active config.
72  $repository = \Drupal::service('entity.last_installed_schema.repository');
73  $definitions = $repository->getLastInstalledFieldStorageDefinitions($entity_type);
74  if (isset($definitions[$field_name]) && $definitions[$field_name] instanceof FieldStorageConfigInterface) {
75    $definitions[$field_name]->setSetting('max_length', $new_length);
76    $repository->setLastInstalledFieldStorageDefinitions($entity_type, $definitions);
77  }
78
79  // 5. Clear cached definitions so the new schema takes effect immediately.
80  \Drupal::service('entity_type.manager')->clearCachedDefinitions();
81  \Drupal::service('entity_field.manager')->clearCachedFieldDefinitions();
82}
83

Verification

Once the code is in place, execute the update sequence via the terminal to apply the schema migration and clear the container caches:

# Run post-update hooks to execute our manual migration
drush updatedb
# Rebuild caches to force the entity field manager to pick up definitions
drush cache:rebuild
# Export your configuration so your field.storage.*.yml matches the state
drush config:export

These steps can also be executed via the Drupal UI, though the CLI approach is faster. Afterwards, review the Drupal status report (/admin/reports/status) to verify that all errors related to field configuration are resolved.

Important Notes / Caveats

  • Database Drivers: This strategy uses standard Drupal schema API syntax (changeField()) and works seamlessly on MySQL, MariaDB, and PostgreSQL. If you are using SQLite, altering column lengths behaves differently since SQLite doesn't enforce strict string limits, but metadata synchronization is still required.
  • Views Integration: If the field is currently used in Views as an argument or filter, the Views cache needs to be cleared (drush cache:clear views). The view configuration itself doesn't track character length directly, so it will continue working without modification.
  • Form Validation: Keep in mind that changing the field storage setting only alters the database-level allowance. If your field instance configurations override or constrain inputs elsewhere (such as via specialized widgets or custom validation hooks), ensure those constraints are reviewed.
Steffen Rühlmann
  • Senior Drupal Developer

Steffen is a Drupal allrounder, with many years of experience in module development, as well as theming and sitebuilding. He is also Acquia certified.