Skip to main content
Home
Drupal life hacks

Main navigation

  • Drupal
  • React
  • WP
  • Contact
  • About
User account menu
  • Log in

Breadcrumb

  1. Home

🧱 Drupal 11.3: Themes Now Support OOP Hooks

By admin, 27 October, 2025

🚀 Introduction

Drupal 11.3.0 brings a powerful update for themers and front-end developers:
themes can now implement hooks using Object-Oriented Programming (OOP) with the #[Hook()] attribute — just like modules have been able to do since Drupal 11.

This means no more long procedural functions in template.php.
Instead, you can organize your logic cleanly in classes inside the theme’s src/Hook directory.


🧩 How It Works

The Drupal core team has added support for OOP hooks in themes.
Under the hood:

  • Each theme now has its own PHP namespace registered in the service container.
  • During installation or uninstallation, the ThemeInstaller resets the container so that new namespaces are recognized.
  • Hooks are automatically discovered through the #[Hook()] PHP attribute.

💡 Basic Example

Old procedural approach (template.php):

function mytheme_preprocess_page(&$variables) {
  $variables['custom_message'] = 'Hello from procedural hook!';
}

New OOP approach (src/Hook/PageHooks.php):

<?php

namespace Drupal\mytheme\Hook;

use Drupal\Core\Hook\Attribute\Hook;

final class PageHooks {

  #[Hook('preprocess_page')]
  public function preprocessPage(array &$variables): void {
    $variables['custom_message'] = 'Hello from OOP hook!';
  }

}

That’s it — Drupal automatically discovers this class and runs the method when hook_preprocess_page() is invoked.


🧭 File Structure

To use OOP hooks, follow this directory convention:

themes/custom/mytheme/
├── mytheme.info.yml
├── templates/
│   └── page.html.twig
└── src/
    └── Hook/
        └── PageHooks.php

The src/Hook folder and the namespace Drupal\mytheme\Hook are required.


🧠 Differences from Module Hooks

While themes and modules now share a similar syntax, there are some key differences:

FeatureModulesThemes
#[Hook()] Attribute✅ Supported✅ Supported
order and module Parameters✅ Supported❌ Not supported
ReorderHook✅ Yes❌ No
RemoveHook✅ Yes❌ No
Hook Invocation OrderCustom (via order)Fixed: active theme → base themes (reverse order)

🧩 Supported Hooks in Themes

Themes only support a subset of hooks — mainly those related to rendering, forms, assets, and preprocessing.

Alter Hooks

css_alter
js_alter
js_settings_alter
library_info_alter
form_alter
form_BASE_FORM_ID_alter
form_FORM_ID_alter
element_info_alter
page_attachments_alter
theme_registry_alter
hook_theme_suggestions_alter
hook_theme_suggestions_HOOK_alter
views_ui_display_tab_alter
views_ui_display_top_alter
plugin_filter_layout_alter
plugin_filter_layout__layout_alter

Regular Hooks

hook_views_pre_render
hook_views_post_render
hook_theme
hook_preprocess
hook_preprocess_HOOK

Contrib modules may add support for additional hooks if they integrate with ThemeManager->alter() or ThemeManager->alterForTheme().


🧰 Example: Moving All Scripts to the Footer

Let’s implement an alter hook that moves JavaScript files to the footer — using the new OOP syntax.

<?php

namespace Drupal\mytheme\Hook;

use Drupal\Core\Hook\Attribute\Hook;

final class AssetHooks {

  #[Hook('js_alter')]
  public function moveJsToFooter(array &$javascript): void {
    foreach ($javascript as &$item) {
      if (isset($item['group']) && $item['group'] !== JS_DEFAULT) {
        $item['group'] = JS_DEFAULT;
      }
    }
  }

}

This replaces the old procedural mytheme_js_alter() function with a neat class-based version.


🎨 Example: Adding a Custom Theme Suggestion

You can also use OOP hooks for template suggestions.

<?php

namespace Drupal\mytheme\Hook;

use Drupal\Core\Hook\Attribute\Hook;

final class SuggestionsHooks {

  #[Hook('theme_suggestions_page_alter')]
  public function addCustomSuggestion(array &$suggestions, array $variables): void {
    if (!empty($variables['user']) && $variables['user']->isAuthenticated()) {
      $suggestions[] = 'page__authenticated';
    }
  }

}

Now you can create page--authenticated.html.twig to customize the layout for logged-in users.


⚠️ Limitations and Edge Cases

Some features and cases are not supported:

  • ❌ Themes cannot implement OOP hooks on behalf of other themes.
  • ❌ Modules cannot implement hooks on behalf of themes.
  • ❌ ReorderHook and RemoveHook are not available in themes.
  • ⚙️ Only certain hooks (mostly alters and preprocess) are supported by default.

🧩 Deprecated Kernel Methods

As part of this change, several DrupalKernel methods have been deprecated:

  • getModulesParameter()
  • getModuleFileNames()
  • getModuleNamespacesPsr4()

These were replaced by the new internal container mechanisms that now also handle themes.


✅ Summary

✅ New CapabilityThemes can now use OOP hooks
📦 File locationsrc/Hook/*.php
🧭 NamespaceDrupal\[themename]\Hook
⚙️ RegistrationAutomatically handled via the container
🧩 Attribute#[Hook('hook_name')]

This change modernizes theming in Drupal, giving front-end developers the same structured and testable approach backend developers have enjoyed since 11.0.

Tags

  • #Drupal Planet
  • Drupal 11
  • Themes
  • Hooks
  • OOP
  • PHP Attributes
  • Add new comment

Comments1

About text formats

Restricted HTML

  • Allowed HTML tags: <a href hreflang> <em> <strong> <cite> <blockquote cite> <code> <ul type> <ol start type> <li> <dl> <dt> <dd> <h2 id> <h3 id> <h4 id> <h5 id> <h6 id>
  • Lines and paragraphs break automatically.
  • Web page addresses and email addresses turn into links automatically.

Adam (not verified)

6 days 5 hours ago

Thank you

Great article. Reworking my theme to OOP now.

Slava Ukraini!

  • Reply
Powered by Drupal