Sorry, you need to enable JavaScript to visit this website.
Skip to main content
Welcome to our website! Explore our services and portfolio.

How the Hook System Works in Drupal

Submitted by admin on

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

  1. The Trigger: Drupal Core (or a contrib module) calls a function like ModuleHandler::invokeAll('node_insert', [$node]).

  2. The Search: Drupal looks through its internal cache of function names for any that end in _node_insert.

  3. The Execution: If your module is named lightning and you have a function lightning_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).

hook_menu_link_defaults

Alter Hooks

Used to change data that has already been defined by another module.

hook_form_alter, hook_link_alter

Action Hooks

Used to react to an event that just happened (CRUD operations).

hook_entity_insert, hook_user_login


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).3

     

  • Events 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 services.yml.

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 (.module file).

Object-Oriented (Classes).

Performance

Very slightly faster.

More overhead but more scalable.

Flexibility

Order determined by module weight.

Order determined by priority (e.g., 10).

Standard

Unique to Drupal.

Standard Symfony/PHP pattern.


Why choose Events?

  1. Strict Typing: You know exactly what object type you are receiving in the subscriber.

  2. Decoupling: Your logic is kept in clean classes rather than one giant .module file.

  3. 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.