Sorry, you need to enable JavaScript to visit this website.
Skip to main content
Welcome to our website! Explore our services and portfolio.

Making Database Updates Using hook_update_n in Drupal

Submitted by admin on

 

drush ev 'print \Drupal::keyValue("system.schema")->get("crystalbit_entity_updates") . "\n";'
drush ev "\Drupal::keyValue('system.schema')->set('crystalbit_entity_updates', (int) 11102)";

 

function MYMODULE_update_9001() {
 $modules_to_disable = ['module_name', 'other_module_name', 'another_module_name',];
 $config_name = 'core.extension';
 // Load the existing configuration.
 $config = \Drupal::configFactory()->getEditable($config_name);
 // Get the current list of enabled modules.
 $enabled_modules = $config->get('module') ?? [];
 // Disable the specified modules.
 foreach ($modules_to_disable as $module) {
   if (isset($enabled_modules[$module])) {
     unset($enabled_modules[$module]);
   }
 }
 // Update the configuration with the new list of enabled modules.
 $config->set('module', $enabled_modules);
 $config->save();
}

 








 

In Drupal, hook_update_n is the standard way to perform database schema changes, data migrations, or configuration updates across environments. These functions are stored in your module's .install file and are triggered when you run drush updb or navigate to /update.php.

The "n" in the function name follows a specific numbering convention: module_update_8001.

  • 8: The core version (e.g., 8, 9, or 10).

  • 0: The major release version.

  • 01: The sequential number of the update.


1. Basic Structure

Add this to your my_module.install file.

PHP

/**
 * Add a new 'status' column to the 'my_custom_table' table.
 */
function my_module_update_9001() {
  $schema = \Drupal::database()->schema();
  
  $spec = [
    'type' => 'int',
    'description' => 'The status of the entry.',
    'length' => 1,
    'not null' => TRUE,
    'default' => 0,
  ];

  $schema->addField('my_custom_table', 'status', $spec);
}

2. Using the Batch-like functionality in Updates

If you need to update thousands of records (e.g., a data migration), you should not do it all at once, or the script will timeout. hook_update_n supports a $sandbox variable that works exactly like the Batch API.

PHP

/**
 * Migrating data from field_old to field_new for all 'article' nodes.
 */
function my_module_update_9002(&$sandbox) {
  $node_storage = \Drupal::entityTypeManager()->getStorage('node');

  // Initialize the sandbox on the first run.
  if (!isset($sandbox['total'])) {
    $nids = $node_storage->getQuery()
      ->condition('type', 'article')
      ->accessCheck(FALSE)
      ->execute();
    
    $sandbox['total'] = count($nids);
    $sandbox['ids'] = array_values($nids);
    $sandbox['current'] = 0;
  }

  $nodes_per_batch = 25;
  $batch_ids = array_slice($sandbox['ids'], $sandbox['current'], $nodes_per_batch);

  foreach ($node_storage->loadMultiple($batch_ids) as $node) {
    // Perform data logic
    $old_value = $node->get('field_old')->value;
    $node->set('field_new', $old_value);
    $node->save();
    
    $sandbox['current']++;
  }

  // Tell Drupal how far along we are. 
  // When #finished reaches 1, the update is complete.
  $sandbox['#finished'] = ($sandbox['current'] >= $sandbox['total']) ? 1 : ($sandbox['current'] / $sandbox['total']);

  return t('Processed @current of @total nodes.', [
    '@current' => $sandbox['current'],
    '@total' => $sandbox['total'],
  ]);
}

3. Common Update Tasks

Task

Method to use

Add a Table

$schema->createTable('name', $table_spec);

Drop a Column

$schema->dropField('table', 'column');

Change Column Type

$schema->changeField('table', 'column', 'new_name', $spec);

Update Config

\Drupal::configFactory()->getEditable('module.settings')->set('key', 'val')->save();


4. Best Practices

  1. Never Change an Existing Update: Once a hook_update_n has been deployed and run on a server, do not edit it. If you need to fix a mistake, write a new update function with a higher number.

  2. Access Check: When using entity queries in updates, always use ->accessCheck(FALSE) because updates often run as an anonymous user via CLI.

  3. Dependencies: If your update depends on another module's update being run first, you can't strictly enforce it via code numbering, but you can check for the existence of tables or configuration before proceeding.

Pro-Tip: The "Entity Definition" Update

If you added a new field via a .yml file and want Drupal to recognize the new schema for an existing entity type, you might need the entity_definition_update_manager service:

PHP

$update_manager = \Drupal::entityDefinitionUpdateManager();
$field_storage_definition = $update_manager->getFieldStorageDefinition('my_field', 'node');
$update_manager->installFieldStorageDefinition('my_field', 'node', 'my_module', $field_storage_definition);





Additional later discussion: how to use hook_post_update_NAME and how it differs from hook_update_n