Drupal 9: Different Update Hooks And When To Use Them

Drupal 9: Different Update Hooks And When To Use Them

5th June 2022 - 20 minutes read time

I have written lots of detail about using update hooks to manage updates in Drupal and they have all been about the hook_update_N() hook. The hook_update_N() hook is just one of the options available in running updates as the update pipeline also includes hook_post_update_NAME(). The hook_deploy_NAME() hook, bundled with Drush 10, can also be used as an update hook in the same way.

Each of these update hooks has a number of different best practices when considering their use. All of these hooks are run once and once only and the key idea is that they take Drupal (or a module) from one version to another by adding database changes or configuration updates as the module gets updated.

For example, if you have a module that has a database table then it will be stored as schema information within your module. Once you release the module you must ensure that everyone who already has the module installed can still use it after the schema has changed. This means that as well as updating the schema information you also need to provide steps in the update hooks to update existing installs. Without this step the module would likely crash as it attempts to inject data into tables or fields that don't exist.

Update hooks can also be used to introduce changes to sites by managing configuration and content. This allows complex changes to be deployed in a clean and predictable manner. It can even be used to deploy content changes like adding menu items or taxonomy terms or even adding content to new fields.

All update hooks have access to a $sandbox parameter. This can be used to create batch processes in your update hooks in order to allow lots of information to be processed at once.

I thought I would take a look at the different ways that it's possible to apply updates to a Drupal site, and some strategies and best practices of running each one.

hook_update_N()

This is intended to introduce a change between different versions of Drupal or contributed modules. The hook has been a part of Drupal since at least version 6 so there are plenty of examples of it in use.

These hooks must be added to your modules *.install file.

It is important to realise that when using this hook you have a lower level of Drupal bootstrap than you are used to. This means that you shouldn't rely on higher level API features or services being available. If you need to perform some actions on content or a service then try to do that in a hook_post_update_NAME() hook as you might not have access to the needed services.

As Drupal is not fully bootstrapped a service called "entity.definition_update_manager" can be used to facilitate the fetching of definitions from the system. You can use \Drupal::entityDefinitionUpdateManager()::getEntityType()  to get information on entities and and \Drupal::entityDefinitionUpdateManager()::getFieldStorageDefinition() to get information on fields.

This is especially the case if you are running an update with multiple modules involved. As you can't rely on those modules being updated before your update hook then it is risky to use them as they might break.

You should also be aware that it is possible to force some update hooks to be run before other update hooks using the hook_update_dependencies() hook. This means that you can't absolutely rely on the order of your update hooks. For that reason it's a good idea to test and retest updates before you release them.

The basic structure of an hook_update_N() is as follows.

/**
 * An example hook_update_N() hook.
 */
function mymodule_update_9001() {
    return (string) new TranslatableMarkup("Update hook triggered.");
}

The docblock before the function is important as this is what is printed out to the user viewing the update hooks they are about to run. Any whitespace is removed from this block so you can still adhere to coding standards and not exceed lines of 80 characters.

To print something out to the user after the hook has fired you can return a string from the update hook. Note that we must create a new TranslatableMarkup() object here since we don't have access to the t() function in this update hook.

The name of the hook is dependent on a number of factors.

  • The first number is the version of Drupal being operated on, so this would be 8, 9 or 10.
  • The second number is the major version of the module being updated.
  • The final two digits are the sequential update hooks being applied. Note that this must start from 1 in order for the hook to be picked up. In other words naming the update hook "9000" won't work.

Here are some do's and don'ts of using hook_update_N().

Do use hook_update_N() to:

  • Add a docblock comment to properly inform what the update hook is doing.
  • Try to do one thing per update hook. It is fine to add multiple fields in a single hook, but don't add fields, update configuration and perform other tasks in a single update hook.
  • Use hook_update_N() hooks to alter or add tables in the database.
  • Add the new field information directly to the update hook, do not pull the information from the module schema. This will help prevent errors in the future if the database schema changes again as the update hook will still contain the correct information.
  • Use hook_update_N() hooks to add new configuration items that might be required by upstream update hooks.
  • When saving configuration pass the $has_trusted_data parameter to the save() method of the configuration object. This will skip any schema definition checks that might lead to incorrect errors if the schema has been changed since the update was written.
  • Always run checks on your actions before you run them. For example, if you are about to add a field then check to make sure that the field doesn't exist already. This will prevent update hooks from failing in unpredictable ways.
  • Use the methods via \Drupal::entityDefinitionUpdateManager()::getEntityType() or \Drupal::entityDefinitionUpdateManager()::getFieldStorageDefinition() to get the current versions of entities and fields from the system.
  • Return a string from the function if you want to print a message from the update hook. This is useful if you want to inform users that something happened or have that message printed in logs.
  • Use the $sandbox variable to batch processing updates that might take a while to complete.

Don't use hook_update_N() to:

  • Do not perform too many actions in update hook. They should not process for a long time, even with the $sandbox variable. If you find that your update hook has to do a lot then try and move it into a Drush command or process the data through a queue process (using the cron).
  • Do not rely on CRUD operations working correctly in this hook. As some services might not be full loaded you might find that some upstream triggers or services are not used when saving items to the system. As the database layer is loaded you can default to using queries to inject data into the right places, but this is not ideal.
  • Do note rename update hooks. Drupal keeps an internal registry of what update hooks are run, so if you rename them there is a chance that update hooks you intended to be run will not be.
  • Do not rely on update hooks being run in other modules, especially in the contributed space. You will often find that modules are updated one at a time on Drupal sites, which means that you can't rely on other update hooks firing first, even when using hook_update_dependencies().

When should you use a hook_update_N() hook?

  • Adding new database tables that your module would otherwise install when first installed. 
  • Any changes to the database tables that need to happen.
  • Adding or removing configuration items that need to happen before you can import the configuration. For example, you might want to add a new field so that you can inject content into it. Adding the field is possible through injecting configuration in this hook.
  • Fixing corrupt configuration.
  • Database queries that alter content and entities can be problematic in contributed modules, but if you are managing a Drupal site then this hook can be a good option to update the database.
  • Getting things ready for the hook_post_update_NAME() update hooks to run (see next section).

See the hook_update_N() documentation page.

hook_post_update_NAME()

The hook_post_update_NAME() hooks are run after the hook_update_N() hooks have completed. At this point Drupal is fully bootstrapped, which means you have access to all services within the Drupal site.

The important thing to remember is that this hook is still run before the configuration is imported. So if you need to add content to a field then you'll need to inject the field configuration before you can add the content.

These hooks must be placed in a *.post_update.php file in your module.

A basic hook_post_update_NAME() hook looks like this.

/**
 * An example hook_post_update_NAME() hook.
 */
function mymodule_post_update_name() {
    return t("Post update hook triggered.");
}

Just like the hook_update_N() hooks, the docblock is used for the description of the hook and the returned string is printed out after the hook has been run. The difference here is that we have access to the t() function and can use it freely in this hook.

The name of this hook is arbitrary, which means you can add anything you want to the end of the hook declaration. These hooks are sorted alpha numerically and Drupal ensures that they are run once only. If you do need to run different hooks in sequence then you can either use a number or even prefix the name with a number.

Here are some do's and don'ts of using hook_post_update_NAME().

Do use hook_post_update_NAME() to:

  • As Drupal is fully bootstrapped you have access to all of the services you need. This means you can use any service within the Drupal site without having to work around limitations.
  • Since this hook is fired after the hook_update_N() hooks you can build upon actions that have taken place in those hooks.
  • Use this hook to inject content into your site. Remember that the configuration hasn't been imported yet so if you need to inject into new fields you'll need to add those fields in through injecting configuration first.
  • Use a number (or a number prefix) on the hook definition to allow these hooks to run in a predictable manner. There is no guarantee that one hook will run before the other, so by naming them correctly you can ensure that the hooks will trigger in a predictable manner.
  • As a cache clear operation will be performed when this hook is run you can add an empty hook_post_update_NAME() hook to trigger a cache clear.
  • Use this hook to delete entities that need to be removed before the configuration is imported. Drupal will halt a configuration import if you are trying to delete configuration for entities that has data present in the system. By using this hook you can delete that data to ensure the import process works correctly.

Don't use hook_post_update_NAME() to:

  • Do not perform any actions that rely on configuration being present since it might not have been imported. You'll have to inject the configuration if you want to use it here.

When should you use a hook_post_update_NAME() hook?

  • Since you have full access to the Drupal API you can pretty much do what you want to in this hook. The configuration won't have been imported yet, so be careful to create that configuration first if you want to use it.

See the hook_post_update_NAME() documentation page.

hook_deploy_NAME()

This is a new hook that is part of the Drush project (added in Drush 10.3) and must therefore be triggered using a Drush command.

There has been a bit of discussion in the Drupal community in recent years about adding a post config import hook. Since Drush has hook_deploy_NAME() that discussion has moved to including Drush as a composer dependency in recommended project file, rather than creating a new hook that does the same thing.

This hook_deploy_NAME() hook is run through a Drush command, 

drush deploy:hook

These hooks must be placed in a *.deploy.php file in your module.

The deploy hooks are executed in alphabetical order. This means that they use the same rules as the hook_post_update_NAME() hooks so prefix the name with a number if you want them to trigger in a predictable manner.

This is a basic deploy hook that would be added to the *.deploy.php file.

/**
 * Drush deploy hook example.
 */
function mymodule_deploy_a() {
    return t('Deploy hook triggered');
}

Again, the docblock comment here shows what the hook will do and the return string will be printed when the hook has triggered.

Here are some do's and don'ts of using hook_deploy_NAME().

Do use hook_deploy_NAME() to:

  • As the full Drupal system and all of the site configuration is imported you can do whatever you need to do in this hook. There is no need to import configuration prior to making any changes.
  • Just like the hook_post_update_NAME hook, you should use a number (or a number prefix) on the hook definition to allow these hooks to run in a predictable manner.

Don't use hook_deploy_NAME() to:

  • This is a non-standard mechanism for running updates on Drupal sites. For this reason you should not use this in contributed modules. At least, not yet. For that reason you need make your update work using just Drupal core update hooks. If you are running a Drupal site then you should be making full use of this hook.

When should you use a hook_deploy_NAME() hook?

  • If you want to create some entities that rely on a certain structure being in place before they can be created.

Conclusion

There are a few options available to you when updating things in Drupal. If you are updating a contributed module then you need to stick with the core update hooks and inject any configuration that you depend upon. If you are running a Drupal site then you can also make use of the Drush hook_deploy_NAME() hook.

In an ideal world your deployment process would contain the following steps.

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

After running this your site will be fully updated and all hooks present will have run. Running this a second time will do nothing (aside from flushing caches) since Drupal will only run these hooks once and once only.

The Drush documentation for the deploy command has a nice little table that shows the three types of update hooks available and what is possible in each. I have reproduced it here since it's a useful synopsis of the limitations of the hooks.

FunctionDrupal APIPurpose
hook_update_N()ReducedLow level changes.
hook_post_update_NAME()FullRuns before configuration is imported.
hook_deploy_NAME()FullRuns after configuration is imported,

I have made use of all of these update hooks and have recently added the deploy step to my deployment process to make use of the Drush hooks. The deploy hook certainly makes life easier when attempting to inject new content or other entities into a site that have been added through configuration.

One thing I would stress is that you should be testing the entire process from start to finish to ensure that all of the hooks trigger correctly. This is why staging environments are so critical to the success of a project as it allows you to perform these (potentially dangerous) operations before running them on production.

Do you have any more hints and tips about running these hooks? Please add them to the comments below.

More in this series

Add new comment

The content of this field is kept private and will not be shown publicly.