Hooks and Events
When to Use
Reference this when looking for extension points to react to Group events without altering core Group logic.
Decision
| Need | Use | Why |
|---|---|---|
| Alter group operations block | hook_group_operations_alter() |
Only Group-specific hook |
| React to relationship create/delete | hook_ENTITY_TYPE_insert/delete on group_relationship |
No dedicated Group event for relationship CRUD |
| Dynamic permissions | permission_callbacks in .group.permissions.yml |
Called at permission cache build time |
| Alter routes | Event subscriber on RoutingEvents::ALTER |
Standard Symfony routing |
| Custom permission source | AccessPolicyBase subclass tagged access_policy |
4.x replaces flexible_permissions_calculator |
Hooks Are OOP in 4.x
Group 4.x has no .module file. All hook implementations are object-oriented methods on hook classes in src/Hook/ (CoreHooks, EntityHooks, FieldHooks, FormHooks, QueryHooks, ThemeHooks, TokenHooks, UserHooks, ViewsHooks), each method tagged with the core #[Hook('hook_name')] attribute. This does not change how you implement your own hooks — your module can still use either procedural .module functions or OOP hook classes. It only matters if you previously called or referenced Group's procedural hook functions directly (e.g. group_entity_access()), which no longer exist.
3.x: Group 3.x implemented these hooks procedurally in
group.module.
Available Hooks (group.api.php)
hook_group_operations_alter(array &$operations, GroupInterface $group) — alter the operations shown in the group_operations block. Called in GroupOperationsBlock::build().
function mymodule_group_operations_alter(array &$operations, GroupInterface $group) {
// Remove the leave operation for a specific group type.
if ($group->bundle() === 'company') {
unset($operations['group-leave']);
}
}
Standard Drupal Hooks Used by Group
Group also uses the following standard hooks for integration:
| Hook | Purpose |
|---|---|
hook_entity_bundle_info() |
Registers bundles for group_config_wrapper for installed config entity plugins |
hook_entity_access() |
Injects Group's access checks via EntityHooks::entityAccess() |
hook_entity_delete() |
Auto-deletes relationships and config wrappers when related entities are deleted |
hook_entity_field_access() |
Hides the group_roles field for non-admins on membership forms |
hook_form_alter() |
Enhances the entity-create form used when adding a brand-new entity to a group, via the group.create_form_enhancer service (CreateFormEnhancer). The 2-step wizard it altered in 3.x is gone. |
hook_modules_installed() |
Installs enforced plugins on new group types |
hook_query_entity_query_alter() |
Adds access conditions to entity queries |
hook_rebuild() |
Runs installEnforced() after a rebuild |
hook_user_cancel() |
Reassigns or deletes groups when a user is cancelled |
hook_user_delete() |
Deletes groups owned by a deleted user |
hook_ENTITY_TYPE_update(user) |
Resets the group role cache when a user's global roles change |
hook_views_query_alter() |
Adds access conditions to Views SQL queries |
Event Subscribers
Group registers the following event subscribers:
| Service ID | Class | Listens to | Purpose |
|---|---|---|---|
group.anonymous_user_response_subscriber |
AnonymousUserResponseSubscriber |
KernelEvents::RESPONSE (priority 5) |
Adds permission cache tags to responses for anonymous users when user.group_permissions context is in use |
group.admin_path.route_subscriber |
GroupAdminRouteSubscriber |
RoutingEvents::ALTER |
Optionally moves group admin routes to use the admin theme |
group.latest_revision.route_subscriber |
GroupLatestRevisionRouteSubscriber |
RoutingEvents::ALTER |
Adds latest revision routes for groups |
group.config_subscriber |
ConfigSubscriber |
ConfigEvents::SAVE |
Clears plugin caches when group type config changes |
Group 4.x has no revision route subscriber of its own — plain revision routes come from Drupal core's revision UI, which is why drupal/entity was dropped as a dependency. There is no group.revision.route_subscriber service and no GroupRevisionRouteSubscriber class at tag 4.0.0-alpha2; do not write code that expects one.
Reacting to Relationship Creation/Deletion
There is no dedicated Group-specific event for relationship CRUD. Use standard entity hooks:
/**
* Implements hook_ENTITY_TYPE_insert() for group_relationship.
*/
function mymodule_group_relationship_insert(GroupRelationshipInterface $relationship) {
if ($relationship->getPluginId() === 'group_node:article') {
// React to an article being added to a group.
$group = $relationship->getGroup();
$node = $relationship->getEntity();
}
}
/**
* Implements hook_ENTITY_TYPE_delete() for group_relationship.
*/
function mymodule_group_relationship_delete(GroupRelationshipInterface $relationship) {
// React to removal.
}
Extending the Permission System
Register a callable in your .group.permissions.yml under permission_callbacks:
// MyModuleGroupPermissions.php
namespace Drupal\mymodule;
class MyModuleGroupPermissions {
public function permissions(): array {
$permissions = [];
foreach (MyEntityType::loadMultiple() as $id => $type) {
$permissions["manage $id content"] = [
'title' => t('Manage @type content', ['@type' => $type->label()]),
'allowed for' => ['member'],
];
}
return $permissions;
}
}
Common Mistakes
- Trying to listen to Group-specific events via a tagged event subscriber. Group does not dispatch custom Symfony events for relationship changes. Use entity hooks instead.
- Forgetting that
EntityHooks::entityDelete()auto-deletes all relationships. If you need to react before deletion, implementhook_ENTITY_TYPE_predeleteon the entity type being removed from the group.
See Also
- Plugin System
- PHP API
- Reference:
web/modules/contrib/group/group.api.php