🔄 Updating hook_help() When Migrating from Drupal 8 to Drupal 11.2
With the release of Drupal 11.2, developers are encouraged to modernize their codebase by replacing legacy hooks with more structured, object-oriented alternatives. One of the most commonly used hooks in legacy modules is hook_help() — the function that adds helpful module documentation to the /admin/help page.
In this article, we'll walk through how to modernize hook_help() when upgrading your module from Drupal 8 to Drupal 11.2, using the new attribute-based system.
🧩 What is hook_help()?
hook_help() allows your module to provide contextual help text for site administrators, typically displayed under the "Help" section of the admin interface. It’s useful both for users and developers trying to understand the purpose and configuration of your module.
🔴 Drupal 8 Style (Legacy)
In Drupal 8, hooks were implemented procedurally using PHP functions. Here's what hook_help() typically looked like:
use Drupal\Core\Routing\RouteMatchInterface;
/
* Implements hook_help().
*/
function hello_world_help($route_name, RouteMatchInterface $route_match) {
switch ($route_name) {
case 'help.page.hello_world':
$output = '';
$output .= '<h3>' . t('About') . '</h3>';
$output .= '<p>' . t('This is an example module.') . '</p>';
return $output;
}
}
This pattern still works in Drupal 9 and 10, but Drupal 11 introduces a modern alternative based on PHP attributes.
✅ Drupal 11.2 Style (Recommended)
Starting in Drupal 11, you can implement hooks using PHP attributes and OOP class methods. This approach is cleaner, more modular, and more aligned with Symfony-based architecture.
Here's how hook_help() looks in modern Drupal:
namespace Drupal\hello_world\Hook;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Hook\Attribute\Hook;
/
* Hook implementations for the Hello World module.
*/
class HelloWorldHooks {
/**
* Implements hook_help().
*/
#[Hook('help')]
public function help(string $route_name, RouteMatchInterface $route_match): ?string {
switch ($route_name) {
case 'help.page.hello_world':
$output = '';
$output .= '<h3>' . t('About') . '</h3>';
$output .= '<p>' . t('This is an example module.') . '</p>';
return $output;
}
return null;
}
}
📁 Where Should the Class Go?
Drupal expects hook classes to live in:
modules/custom/hello_world/src/Hook/HelloWorldHooks.php
Once you create or update the file, clear the cache to let Drupal pick up the new hook:
drush cr
🤔 What If My Module Has Many Hooks?
You can split your hooks by type for better organization:
src/Hook/EntityHooks.php
src/Hook/FormHooks.php
src/Hook/SystemHooks.php
Drupal will scan all classes for methods marked with #[Hook()], so naming is flexible.
📌 Why Use the Attribute-Based System?
| Legacy Hook (Drupal 8) | Modern Hook (Drupal 11.2) |
|---|---|
Procedural PHP in .module | Object-oriented PHP in src/Hook/ classes |
| All hooks in one file | Hooks can be split into multiple classes |
| Manual registration | Auto-discovered via attributes |
| Global function names | Namespaced methods — no conflicts |
🧪 Quick Comparison Table
| Drupal Version | Hook Style | Preferred? |
|---|---|---|
| 8.x | Procedural | ✅ |
| 9.x | Procedural (still OK) | ✅ |
| 10.x | Procedural + OOP | ⚠️ Mixed |
| 11.x | PHP Attribute + OOP | ✅✅✅ |
🏁 Conclusion
Migrating to Drupal 11.2 is a great opportunity to modernize your custom modules and align them with current best practices. Using the new attribute-based #[Hook] approach makes your code more maintainable, organized, and future-ready.
Start with hook_help() — it’s a simple change, but one that opens the door to a fully OOP, service-oriented architecture in your module.
📨 Need help migrating your custom modules to Drupal 11? Get in touch — we can help you modernize your codebase step by step.
Comments1
hooks in themes
Hey, what about hooks in themes?