Using Drupal hooks for module interaction
Drupal's hook system gives modules a controlled way to react to another module's behaviour and change data before other code consumes it. A hook is a named extension point implemented as a PHP function, usually in a .module file. When Drupal reaches that point, its module handler finds the enabled implementations and calls them in a defined order. This lets one module respond when content is saved while another decides how that content is presented.
For isolated adjustments, a procedural hook remains practical. It fits form alterations, entity lifecycle events, and small site-specific rules without creating a new service. Larger domain logic usually belongs in a service, plugin, or event subscriber, where dependency injection and unit testing are easier. The examples below show where hook-based module interaction earns its place and where another Drupal extension pattern will produce a more maintainable codebase.
| Drupal mechanism | Best suited to | How modules interact | Main trade-off |
|---|---|---|---|
| Hooks | Small reactions and established extension points | The module handler discovers named callbacks | Procedural and potentially order-sensitive |
| Services | Reusable domain logic and integrations | Injected objects collaborate through APIs | Requires deliberate application architecture |
| Plugins | Replaceable behaviour and configurable types | Plugin managers select available implementations | More setup and definition work |
| Event subscribers | Decoupled workflows and domain reactions | Events are dispatched to interested listeners | Indirect flow can require tracing |
How the hook system works
Hook implementations are discovered by function name. A module called ausie_content implementing hook_form_alter() defines ausie_content_form_alter(). The module handler maintains this registry and rebuilds it when Drupal's caches are cleared. Only enabled modules participate, and a missing implementation simply has no effect.
Drupal also supports service-based integrations and object-oriented plugins. These are not interchangeable labels: a hook coordinates a named Drupal extension point, while a service exposes reusable behaviour and a plugin provides replaceable implementations. When a configurable, field-like structure is required, the custom plugin type guide demonstrates the plugin approach in more detail.
Hooks work best when their contract is narrow. A callback should react to an event, alter a documented value, or return data for the caller to interpret. Unrelated calculations, database transactions, and broad changes to application state make procedural callbacks difficult to reason about.
Finding and invoking hooks
Start with the API documentation for the exact operation rather than guessing a callback name. Drupal exposes thousands of hooks, and several names look similar. The function suffix must match the hook name, while the module prefix identifies the implementation. Using a clear module name such as ausie_content avoids accidental collisions.
use Drupal\Core\Form\FormStateInterface;
function ausie_content_form_alter(array &$form, FormStateInterface $form_state) {
$form['#attributes']['data-market'] = 'au';
}
The module handler can tell you whether implementations exist. This is useful during diagnosis because an enabled module with no matching function will never be called:
$implementations = \Drupal::moduleHandler()->getImplementations('form_alter');
Use the generic form hook only when every form needs the change. A form-specific implementation is easier to test and reduces the chance of altering an unrelated workflow. After adding or renaming a hook implementation, rebuild Drupal's caches so the implementation registry reflects the file. drush cr is the usual command on developer and managed environments. On a restricted hosting account, an administrator can clear caches from the administrative interface instead.
Responding to content changes
Entity lifecycle hooks report actions after or before Drupal saves an entity. For a node, hook_node_insert() runs after a new node is saved, hook_node_update() after an existing node changes, and hook_node_delete() after deletion. Generic entity variants such as hook_ENTITY_TYPE_insert() let one implementation cover a family of content types.
use Drupal\node\NodeInterface;
function ausie_content_node_insert(NodeInterface $node) {
\Drupal::logger('ausie_content')->notice(
'Created node @id in @bundle.',
['@id' => $node->id(), '@bundle' => $node->bundle()]
);
}
The callback receives the entity, so it can inspect its bundle, fields, and workflow state. It should avoid changing the entity again, since that can trigger another save and a loop. If a value must be prepared before persistence, hook_ENTITY_TYPE_presave() is the appropriate point.
Choose lifecycle timing carefully. Insert and update hooks may run inside broader operations that later fail, so they are poor places for irreversible external side effects. For a remote service call, enqueue a message or process the item in a controlled queue, and make retries idempotent. A hook should announce what Drupal changed; the workflow layer can decide what happens next.
Changing values with alter hooks
An alter hook changes data in place before rendering or another consumer uses it. Unlike a notification hook, it receives the value by reference. Drupal provides alter hooks for forms, entities, render arrays, and many specialised extension points, so always use the narrowest hook that matches the job.
use Drupal\node\NodeInterface;
function ausie_content_node_view_alter(array &$build, NodeInterface $node) {
if ($node->bundle() === 'event') {
$build['#attributes']['data-region'] = 'au';
}
}
The narrow hook_form_FORM_ID_alter() variant is preferable to hook_form_alter() when only one form needs adjustment. Likewise, check the entity bundle before changing a node render array. An indiscriminate alteration may affect cached output, accessibility, or another site's expected markup.
Custom modules can define their own event and alteration pair. The caller invokes the event through the module handler, then calls alter() with the same hook name. This preserves a clear contract: implementations provide values, and alter implementations may refine the complete result. Document both signatures so other modules can extend them without reading your source.
Controlling order and keeping changes isolated
Invocation order matters when modules alter the same value. Ordinary hook implementations follow module weight, with lighter modules running earlier. Alter hooks commonly run in reverse weight order so a lower-priority module can have the final say. Dependencies can also constrain which modules are present, but they do not replace an explicit ordering decision.
Module weight belongs in deployment configuration rather than a hard-coded branch in a hook. Keep site-specific settings in Drupal's configuration management and apply them consistently across Sydney, Melbourne, and other environments. A hook may read that configuration, but it should not decide that one Australian deployment behaves differently merely because its hostname changed.
Procedural hooks also have weak isolation: they can call global services, depend on execution order, and be awkward to mock. Put reusable calculations in an injected service, subscribe to domain events when a decoupled workflow is needed, and let the hook translate between Drupal's extension point and that service. This keeps module interaction testable without surrendering Drupal's familiar hooks.
Debugging and testing hook behaviour
Start debugging by confirming that the module is installed and that the implementation appears in the registry. The module handler exposes the discovered callbacks:
$implementations = \Drupal::moduleHandler()->getImplementations('node_insert');
Check the hook name, function prefix, cache rebuild, and whether an earlier callback stopped execution. Use the logging service rather than echoing output. Include identifiers and relevant state, but redact names, addresses, health information, and other personal data. A message such as Changed node @id in @bundle is usually enough to diagnose an integration without creating a privacy risk.
Put custom hook modules under a test module and exercise them with Drupal's PHPUnit or functional test base classes. Create the entity, perform the action, and assert both the callback's observable result and any service interaction. Tests should cover enabled and disabled implementations, altered values, and multiple modules participating in the same hook.
Applying hooks in Australian projects
A Drupal brief in Canberra may require conformance with the Australian Government Digital Service Standard and WCAG 2.1 AA accessibility requirements. Hooks can validate content or adjust form markup, but the rule must not depend on JavaScript alone. Keyboard operation, clear errors, and meaningful labels belong in the form or component that owns them.
Privacy rules are equally concrete. The Privacy Act 1988 and Australian Privacy Principles can limit what an administrative callback stores in logs, analytics, or a temporary record. A hook that reacts to a person or organisation should record the minimum needed for the purpose and define retention, rather than treating Drupal's log as an unlimited archive.
Across Sydney, Melbourne, Brisbane, and Perth, agencies often manage multi-site Drupal estates for government, education, or commercial clients. Australian teams may call a small request a “quick tweak”, but a callback that affects every node has a large blast radius. The local market also mixes contributed Drupal modules with commercially supported products and local maintenance agreements; an agency can buy an SLA, retain regional support, or contribute fixes upstream. The dependable pattern is the same: keep the hook contract small, test it under real Drupal conditions, and put broad business rules in a replaceable service.