Drush Deployment Hooks vs. Drupal Update Hooks: Choosing the Right Tool

Drupal deployments often involve more than deploying code and importing configuration. Sometimes we need to update existing content, migrate stored data, or perform operations that depend on newly deployed configuration.

Drupal provides update hooks for these tasks, but their execution order introduces an important limitation. Drush deployment hooks address that limitation by allowing code to run after configuration has been imported.

Understanding when each hook executes helps prevent deployment failures and makes updates more predictable.

Understanding Drupal's deployment sequence

The drush deploy command provides a standardized deployment workflow. In current versions of Drush, the essential sequence is:

drush updatedb
drush config:import
drush cache:rebuild
drush deploy:hook

Three types of hooks participate in this process:

HookExecutionPrimary purpose
hook_update_N()During updatedbDatabase schema and low-level updates
hook_post_update_NAME()After update hooks, before config importEntity and data updates using Drupal APIs
hook_deploy_NAME()After config importOperations requiring newly deployed configuration

The distinction matters because Drupal's active configuration may be different at each stage of deployment.

Drupal update hooks: Changes before configuration import

Traditional update hooks, implemented in a module's .install file, are intended for low-level changes such as updating database tables or entity storage definitions.

For example:

/**
 * Add a column to a custom database table.
 */
function mymodule_update_10001() {
  $schema = \Drupal::database()->schema();

  $schema->addField('custom_records', 'external_id', [
    'type' => 'varchar',
    'length' => 255,
    'not null' => FALSE,
  ]);
}

These hooks run early in the deployment process, when Drupal's database schema and code may not yet be fully synchronized.

Because of this, developers should avoid using normal entity CRUD operations in hook_update_N(). Those operations can depend on entity definitions, services, or database structures that aren't ready.

Drupal also provides hook_post_update_NAME(), implemented in a module's .post_update.php file. Post-update hooks run after the lower-level updates and have access to Drupal's fully bootstrapped APIs.

They're useful for updating existing entities or performing data transformations that don't depend on configuration introduced by the current deployment.

However, both types of Drupal update hooks execute before configuration import.

That's where deployment hooks become useful.

Drush deployment hooks: Working with newly imported configuration

Drush introduces hook_deploy_NAME(), which runs after configuration has been imported and caches rebuilt.

Deployment hooks live in a custom module's .deploy.php file and execute through drush deploy:hook, typically as the final step of drush deploy.

Consider a common scenario: a deployment introduces a new field on an existing content type, and existing nodes need that field populated.

A traditional update hook isn't appropriate because the field may not exist when drush updatedb runs. A post-update hook has the same problem.

A deployment hook runs after the field configuration has been imported.

For example, in mymodule.deploy.php:

/**
 * Populate the new external_id field on existing articles.
 */
function mymodule_deploy_populate_external_ids(array &$sandbox) {
  $storage = \Drupal::entityTypeManager()
    ->getStorage('node');

  $ids = \Drupal::entityQuery('node')
    ->condition('type', 'article')
    ->notExists('field_external_id')
    ->accessCheck(FALSE)
    ->execute();

  foreach ($storage->loadMultiple($ids) as $node) {
    $node->set('field_external_id', 'article-' . $node->id());
    $node->save();
  }
}

Like post-update hooks, deployment hooks are tracked so they aren't repeatedly executed on subsequent deployments.

This makes them particularly useful for one-time content transformations tied to specific releases.

Choosing the right hook

The decision usually comes down to two questions: what needs to change, and what must already exist for that change to succeed?

Use hook_update_N() for low-level structural changes that must happen before Drupal can safely operate with the new code.

Use hook_post_update_NAME() for entity or data updates that need Drupal's full API but don't depend on newly imported configuration.

Use hook_deploy_NAME() when the operation requires configuration introduced by the same deployment, such as a new field, content type, or taxonomy vocabulary.

There are also operational considerations. Deployment hooks require Drush and a deployment workflow that invokes them. Running drush updatedb alone won't execute them.

Finally, use configuration management for configuration changes whenever possible. A deployment hook should not become a substitute for configuration exports or a general-purpose place to run arbitrary deployment scripts.

Making deployments more predictable

Deployment hooks don't replace Drupal's update system. They extend it by providing an additional execution point after configuration import.

That distinction is especially valuable for complex Drupal sites where changes to structured content frequently require corresponding updates to existing data.

Choosing the right hook helps keep schema changes, configuration management, and content transformations in the correct order. More importantly, it makes deployments easier to test, repeat, and maintain.

Drupal

Read This Next