Read the blog post: A modern alternative to Hooks
Hux is a project specifically designed for developers, allowing hook implementations without needing to define a .module file or any kind of proxy class/service features.
There are a few projects out there that try to introduce an event subscriber driven way of working. Hux is an in between solution, allowing the full benefits of dependency injection and class driven logic without going fully in with events.
Hooks are implemented with classes, wherein methods tagged with attributes are used for discovery. Methods have the same parameter/return signature as original hook implementations. Discovery is automatic, requiring a cache clear for each new class added.
You can also define multiple of the same hook per module!
Requires Drupal 9.4 or later, and PHP 8.0 or later. (Drupal 9.3 users may use a patch in #2616814: Delegate all hook invocations to ModuleHandler)
Usage Examples (See README.md for more)
declare(strict_types = 1);
namespace Drupal\my_module;
use Drupal\hux\Attribute\Alter;
use Drupal\hux\Attribute\Hook;
use Drupal\hux\Attribute\ReplaceOriginalHook;
/**
* Usage examples.
*/
final class MyModuleHooks {
public function __construct(
// Autowiring is enabled.
// \Drupal\Core\DependencyInjection\ContainerInjectionInterface, or manual service definitions are available for older Drupal installs or usage of non-autowirable services.
private readonly \Drupal\Component\Datetime\TimeInterface $time,
)
#[Hook('entity_access')]
public function myEntityAccess(EntityInterface $entity, string $operation, AccountInterface $account): AccessResultInterface {
// A barebones implementation.
return AccessResult::neutral();
}
#[Hook('entity_access', priority: 100)]
public function myEntityAccess2(EntityInterface $entity, string $operation, AccountInterface $account): AccessResultInterface {
// You can set priority if you have multiple of the same hook!
return AccessResult::neutral();
}
#[Hook('entity_access', moduleName: 'a_different_module', priority: 200)]
public function myEntityAccess3(EntityInterface $entity, string $operation, AccountInterface $account): AccessResultInterface {
// You can masquerade as a different module!
return AccessResult::neutral();
}
#[ReplaceOriginalHook(hook: 'entity_access', moduleName: 'media')]
public function myEntityAccess4(EntityInterface $entity, string $operation, AccountInterface $account): AccessResultInterface {
// You can override hooks for other modules! E.g \media_entity_access()
return AccessResult::neutral();
}
#[ReplaceOriginalHook(hook: 'entity_access', moduleName: 'media')]
public function myEntityAccess5(EntityInterface $entity, string $operation, AccountInterface $account, #[OriginalInvoker] callable $originalInvoker): AccessResultInterface {
// If you override a hook for another module, you can have the original
// implementation passed to you as a callable!
$originalResult = $originalInvoker($entity, $operation, $account);
// Do something...
return AccessResult::neutral();
}
#[Alter('user_format_name')]
public function myCustomAlter(string &$name, AccountInterface $account): void {
$name .= ' altered!';
}
#[
Hook('entity_insert'),
Hook('entity_delete'),
]
public function myEntityInsertOrDelete(EntityInterface $entity): void {
// Associate with multiple!
// Also works with Alters and Replacements.
}
}
Contribution tips
Only Merge Requests are accepted. Patches are not accepted.
Tests use a customized Gitlab template with custom PHP Code Sniffer and PHPStan ruleset. You can fix these issues by running linting locally or wait for errors after pushing your work to an MR.
Similar projects
Versions
- 1.1.x is compatible with Drupal 9 and 10.
- 1.2.x, Drupal ^10.0
- 1.3.x, Drupal ^10.1
- 1.4.x, Drupal ^10.2
- 1.5.x, Drupal ^10.3
- 1.6.x, Drupal ^10.3 || ^11
- 1.7.x, Drupal 11.0 minimum
- 1.8.x, Drupal 11.1 minimum
Actual constraints may vary. Older incompatible releases may match an untested version of core.
Project information
- Project categories: Developer tools
592 sites report using this module
- Created by dpi on , updated
Stable releases for this project are covered by the security advisory policy.
Look for the shield icon below.
Releases
🐛 Use Drupal core's module name limit
Development version: 1.8.x-dev updated 15 May 2025 at 08:54 UTC

