How the Hook System Works in Drupal
In Drupal, the Hook System is a pattern based on the "Aspect-Oriented Programming" philosophy.1 It allows modules to "hook into" the core flow of the system to alter data, react to events, or extend functionality without modifying the core code itself.2
Think of Drupal as a train moving along a track; at specific "stations" (hooks), the train stops and asks all active modules, "Does anyone want to add something or change something before I continue?"
1. How It Works (The Lifecycle)
The hook system operates through a naming convention. When Drupal reaches a specific action (like saving a node), it looks for functions in every enabled module that follow a specific pattern: module_name_hook_name.
The Discovery Phase
The Trigger: Drupal Core (or a contrib module) calls a function like
ModuleHandler::invokeAll('node_insert', [$node]).The Search: Drupal looks through its internal cache of function names for any that end in
_node_insert.The Execution: If your module is named
lightningand you have a functionlightning_node_insert(), Drupal executes it and passes the node object to you.
2. Types of Hooks
Hooks generally fall into three categories based on what they are trying to achieve:
Type | Purpose | Example |
Info Hooks | Used to define new "things" to the system (plugins, paths, blocks). |
|
Alter Hooks | Used to change data that has already been defined by another module. |
|
Action Hooks | Used to react to an event that just happened (CRUD operations). |
|
3. The Alter Pattern
Alter hooks are unique because they pass data by reference (denoted by the & symbol). This allows multiple modules to modify the same variable in a chain.
PHP
/**
* Example of an Alter Hook.
* Note the '&' before $form, which allows us to modify the original variable.
*/
function my_module_form_alter(&$form, FormStateInterface $form_state, $form_id) {
if ($form_id === 'user_login_form') {
$form['#title'] = t('Welcome back! Please sign in.');
}
}
4. Hook Order (Weight)
If two modules implement the same hook, Drupal decides which one runs first based on the module's weight in the system table.
Lower weights (e.g., -10) run earlier.
Higher weights (e.g., 10) run later, meaning they often have the "final word" in an alter hook.
5. Hooks vs. Events (Drupal 8, 9, 10+)
Since Drupal transitioned to Symfony, the system uses both Hooks and Event Subscribers.
Hooks are still used for most metadata and entity-specific actions (like
hook_node_presave).3Events are used for more "modern" Symfony-level actions (like routing, kernel requests, and configuration saving).
Pro-Tip: Finding Hooks
You can find a list of available hooks in files ending in .api.php within Drupal core or contributed modules. These files are never executed; they exist purely as documentation to show you the function signature and how to use the hook.
Next, we will see how to define your own custom hook in your module so that other developers can extend your functionality.
Creating your own custom hook is a brilliant way to make your module "pluggable," allowing other developers to modify your module's behavior without touching your code.
To define a custom hook, you primarily use the Module Handler service.
1. Defining and Triggering the Hook
There are two main ways to trigger a hook depending on whether you want to collect data (Action/Info hook) or allow others to modify data (Alter hook).
Case A: The Action Hook (Informing others)
Use invokeAll() when you want to tell other modules that something happened and let them run their own logic.
PHP
// Inside your Service or Controller
$module_handler = \Drupal::moduleHandler();
// Trigger 'hook_my_module_data_processed'
// The second argument is an array of variables passed to the hook.
$module_handler->invokeAll('my_module_data_processed', [$entity, $status]);
Case B: The Alter Hook (Letting others change data)
Use alter() when you have an array or object that you want other modules to modify before you use it.
PHP
$data = [
'price' => 100,
'currency' => 'USD',
];
// Trigger 'hook_my_module_data_alter'
// Note: You don't pass an array; you pass the variable directly.
$module_handler->alter('my_module_data', $data);
// After this line, $data might have been changed by another module.
2. Documenting Your Hook (.api.php)
For other developers to find your hook, you must document it in a file named my_module.api.php in your module root. This file is not executed by PHP; it’s for IDEs and documentation.
PHP
/**
* Provide a description of what the hook does.
*
* @param array $data
* An associative array containing the data to be altered.
*/
function hook_my_module_data_alter(array &$data) {
$data['price'] = 50;
}
3. How Other Modules Implement It
Once you’ve triggered it, another developer (or even you in a different module) can use it just like a core hook:
PHP
/**
* Implements hook_my_module_data_alter().
*/
function some_other_module_my_module_data_alter(&$data) {
if (\Drupal::currentUser()->hasRole('vip')) {
$data['price'] = $data['price'] * 0.8; // 20% discount for VIPs
}
}
4. Hooks vs. Events: Which to choose?
When creating custom "extension points" in modern Drupal, you have a choice:
Feature | Custom Hook | Symfony Event |
Complexity | Very Simple (Procedural). | Moderate (Requires Classes/Subscribers). |
Discovery | Automatic based on function name. | Explicitly registered in |
Developer Familiarity | High for "Old School" Drupalers. | High for Symfony/Modern PHP devs. |
Performance | Slightly faster for single triggers. | Better for complex, decoupled systems. |
Summary of the "Hook" logic
By using $module_handler->alter('something', $variable), you are essentially saying: "Here is my data; I'm opening the door for any other module to change it before I save it to the database."
Next we will see how to implement the Event Dispatcher version of this, which is the more "modern" Symfony way to achieve the same result.
In modern Drupal, using the Event Dispatcher is the preferred way to allow other modules to react to or modify logic within an Object-Oriented context. This follows the Symfony "Observer" pattern.
Unlike hooks, which are discovered automatically via function names, Events must be Dispatched (sent out) and Subscribed to (listened for).
1. Create the Event Class
First, you need a class that "carries" the data. This class acts as the container for whatever information you want to pass around.
src/Event/ProductPriceEvent.php
PHP
namespace Drupal\my_module\Event;
use Symfony\Contracts\EventDispatcher\Event;
/**
* Event that is fired when a product price is being calculated.
*/
class ProductPriceEvent extends Event {
const SET_PRICE = 'my_module.set_price';
protected $price;
public function __construct($price) {
$this->price = $price;
}
public function getPrice() {
return $this->price;
}
public function setPrice($price) {
$this->price = $price;
}
}
2. Dispatch the Event
Now, in your service or controller, you "fire" the event. You need to inject the event_dispatcher service.
PHP
use Drupal\my_module\Event\ProductPriceEvent;
// 1. Get the dispatcher service
$dispatcher = \Drupal::service('event_dispatcher');
// 2. Initialize your event object with data
$event = new ProductPriceEvent(100);
// 3. Dispatch the event
$dispatcher->dispatch($event, ProductPriceEvent::SET_PRICE);
// 4. Get the (potentially modified) data back
$final_price = $event->getPrice();
3. Subscribe to the Event (The Listener)
Other modules (or your own) can now "listen" for this event. This requires two parts: a class and a service registration.
Step A: Register the Subscriber
my_module.services.yml
YAML
services:
my_module.price_subscriber:
class: Drupal\my_module\EventSubscriber\PriceSubscriber
tags:
- { name: event_subscriber }
Step B: The Subscriber Class
src/EventSubscriber/PriceSubscriber.php
PHP
namespace Drupal\my_module\EventSubscriber;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Drupal\my_module\Event\ProductPriceEvent;
class PriceSubscriber implements EventSubscriberInterface {
/**
* Register which events this class wants to listen to.
*/
public static function getSubscribedEvents() {
return [
ProductPriceEvent::SET_PRICE => ['onPriceCalculation', 10],
];
}
public function onPriceCalculation(ProductPriceEvent $event) {
$current_price = $event->getPrice();
// Apply a 10% discount
$event->setPrice($current_price * 0.9);
}
}
Comparison: When to use which?
Feature | Hooks | Events |
Code Style | Procedural ( | Object-Oriented (Classes). |
Performance | Very slightly faster. | More overhead but more scalable. |
Flexibility | Order determined by module weight. | Order determined by priority (e.g., |
Standard | Unique to Drupal. | Standard Symfony/PHP pattern. |
Why choose Events?
Strict Typing: You know exactly what object type you are receiving in the subscriber.
Decoupling: Your logic is kept in clean classes rather than one giant
.modulefile.Multiple Methods: One subscriber class can listen to dozens of different events easily.
Additional future discussion: how to use Events to perform an action every time a Configuration object is saved.
Recent content
-
1 hour 5 minutes ago
-
1 week ago
-
1 week 1 day ago
-
1 week 1 day ago
-
1 week 1 day ago
-
1 week 6 days ago
-
2 weeks ago
-
2 weeks 1 day ago
-
2 weeks 1 day ago
-
2 weeks 2 days ago