Altering entity type definitions with hook_entity_type_alterfor Drupal 8 , 9 , 10 , and 11

Last updated :  

Every entity type on your site — nodes, users, taxonomy terms, your own custom entities — is defined once, in an annotation on a PHP class, and then collected into a single master list before Drupal ever uses any of them. hook_entity_type_alter() is your chance to reach into that master list and adjust one entity type's definition — swap out a handler class, add a new link template, change a form class — without ever touching the original entity class itself.

What you'll learn in this lesson

  • When entity type definitions get built, and why altering them is different from altering a form
  • The shape of the $entity_types array you receive
  • A real example: swapping in a custom access-control handler for an existing entity type

The hook and its timing

use Drupal\Core\Entity\EntityTypeInterface;

/**
 * Implements hook_entity_type_alter().
 */
function example_module_entity_type_alter(array &$entity_types) {
  /** @var \Drupal\Core\Entity\EntityTypeInterface $node_type */
  $node_type = $entity_types['node'];
  // Swap in our own access control handler, which extends the core one
  // and adds an extra check.
  $node_type->setHandlerClass('access', \Drupal\example_module\NodeCustomAccessControlHandler::class);
}

$entity_types arrives as an associative array keyed by entity type ID (node, user, taxonomy_term, and so on), where each value is a fully-built EntityTypeInterface object — not a raw array like a form. This runs once, early, when Drupal is assembling its entity type registry — well before any individual node or user is loaded — which is exactly why it's the right place to change how an entity type behaves, rather than something you'd reach for to affect one specific entity instance.

What you can actually change

Because you're handed a real EntityTypeInterface object with real setter methods, your options are concrete and well-defined rather than freeform array manipulation:

  • setHandlerClass('access', ...), setHandlerClass('storage', ...), setHandlerClass('list_builder', ...), and similar — swap out any of the entity type's handler classes.
  • setLinkTemplate('some-new-template', '/path/{entity}') — add a route link template the original entity type didn't define.
  • setFormClass('some_form_mode', SomeFormClass::class) — attach an additional form mode.
This is cached alongside routing and plugin discovery. Like most "definition assembly" hooks in Drupal, the resulting entity type registry is cached — clear the cache after adding or changing an implementation of this hook before you'll see the effect.

Quick check: you want to change how one specific node behaves — say, hide the delete link only for nodes older than a year. Is hook_entity_type_alter() the right tool? (Answer: no — this hook alters the entity type's definition once, site-wide, before any instance exists. Per-instance logic like "this particular node" belongs in an access-control handler's checkAccess() method or a hook that runs per-entity, like hook_entity_access(), not here.)

Key takeaways

  • hook_entity_type_alter(array &$entity_types) receives fully-built EntityTypeInterface objects keyed by entity type ID — use their real setter methods, not raw array manipulation.
  • It runs once, early, altering how an entity type behaves for every instance of it — not the right place for per-entity conditional logic.
  • Swapping handler classes (access, storage, list builder, view builder) is the most common real-world use.
  • The entity type registry is cached — clear caches after adding a new implementation.

Coming up next

Entity types aren't the only site-wide registry you can reach into before it's used. Next: Implementing hook_menu_links_discovered_alter, where the same pattern lets you edit the entire menu-link registry Drupal assembles from every module's .links.menu.yml file.