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:hookThree types of hooks participate in this process:
| Hook | Execution | Primary purpose |
|---|---|---|
hook_update_N() | During updatedb | Database schema and low-level updates |
hook_post_update_NAME() | After update hooks, before config import | Entity and data updates using Drupal APIs |
hook_deploy_NAME() | After config import | Operations 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.
Read This Next
- Supply Chain Security for Drupal Projects
- Mercury Editor 3: A Better Visual Editing Experience for Drupal
- Extending Editoria11y with Custom Content Checks in Drupal
- Freedom with the form Attribute: More Flexible Drupal Views Exposed Filters
- The Cost of Waiting: Why CMS Maintenance Belongs in Every Website Budget