Hooks
A hook is a place in core or in another module where your module can add its own code. At a certain moment — before a node is saved, before a form is shown — Drupal asks every enabled module whether it wants to step in. That is how site behaviour is changed without editing other people's files.
Where hooks are written
A hook is an ordinary function in the blog.module file in the root of the module folder. The file appeared in the lesson about templates: the blog_theme() function in it is a hook too. The function name follows the rule "module name, underscore, hook name".
The first hook
The hook_entity_presave hook is called before any entity is saved. Let us strip extra spaces from article titles:
// web/modules/custom/blog/blog.module
use Drupal\Core\Entity\EntityInterface;
use Drupal\node\NodeInterface;
/**
* Implements hook_entity_presave().
*/
function blog_entity_presave(EntityInterface $entity): void
{
if ($entity instanceof NodeInterface && $entity->bundle() === 'article') {
$entity->setTitle(trim($entity->getTitle()));
}
}The word hook in the name is replaced with the module name: hook_entity_presave became blog_entity_presave. Run drush cr, create an article with spaces at the start of the title — after saving they are gone. The Implements hook_… comment is an accepted convention that helps to find hooks in the code.
Altering another form
The hook_form_alter hook is called before every form of the site is shown:
// web/modules/custom/blog/blog.module
use Drupal\Core\Form\FormStateInterface;
/**
* Implements hook_form_alter().
*/
function blog_form_alter(array &$form, FormStateInterface $form_state, string $form_id): void
{
if ($form_id === 'node_article_form') {
$form['title']['widget'][0]['value']['#description'] = t('No longer than 60 characters.');
}
}A hint appears under the title field of the article creation form /node/add/article. The form array arrives by reference — the & sign before $form — so the hook changes it in place and returns nothing. The form identifier is visible in its HTML, in the id attribute of the form tag, only with hyphens instead of underscores.
Frequent hooks
| Hook | When it is called |
|---|---|
hook_theme() | Declaring your own templates |
hook_form_alter() | Before any form is shown |
hook_form_FORM_ID_alter() | Before one particular form is shown |
hook_entity_presave() | Before an entity is saved |
hook_entity_insert(), hook_entity_update() | After creation and after a change |
hook_entity_delete() | After deletion |
hook_preprocess_HOOK() | Before a template is rendered: preparing variables |
hook_page_attachments() | Attaching libraries to every page |
hook_cron() | When the scheduler runs |
Capital letters in a name are replaced with a concrete value: hook_form_FORM_ID_alter for the article form is blog_form_node_article_form_alter.
Where to find hooks
All core hooks are described in files ending in .api.php, for example web/core/lib/Drupal/Core/Entity/entity.api.php. They list the arguments and give an example. These files are not executed; they serve as documentation.
Hooks in classes
Starting with Drupal 11.1 a hook can be written as a class method with an attribute — without the blog.module file:
// web/modules/custom/blog/src/Hook/BlogHooks.php
namespace Drupal\blog\Hook;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Hook\Attribute\Hook;
class BlogHooks
{
#[Hook('entity_presave')]
public function entityPresave(EntityInterface $entity): void
{
// the same code as in the function
}
}The class lives in the src/Hook folder, and the hook name is given in the attribute without the hook_ part. This does not work in Drupal 10, so the course uses functions — they work in both versions.
Common mistake
A typo in a function name causes no error: blog_from_alter instead of blog_form_alter is a valid PHP function that nobody ever calls. If a hook "does not fire", check the name letter by letter and run drush cr: the list of hooks of each module is kept in the cache, and Drupal sees a new function only after it is rebuilt.