 [ ← blog / drupal ](/) Aug 22, 2023  ·  \#drupal  ·  4 min read 

# How do theme template overrides in Drupal work?



In Drupal, with themes, you can override default template output by copying, pasting, and customizing those templates. It works, but how? I thought I always knew how this worked. But I realized I didn't when I dove into [supporting Drupal 7 themes with Retrofit](https://mglaman.dev/blog/retrofits-next-milestone-drupal-7-theme-support).

I know Drupal builds the theme registry and theme hook suggestions. In doing so, it scans templates, and part of that process includes scanning the active theme for template and theme hook template implementations. But when reading the `\Drupal\Core\Theme\Registry` code, I was coming up blank.

## Components of Drupal's theme registry

The theme registry in Drupal contains all theming information about defined templates and preprocess hooks. There is a base registry of theme hooks defined by modules. Then, there are versions of the registry for individual themes. That is because each theme may implement preprocess hooks or template overrides.

- `\Drupal\Core\Theme\Registry` – Maintains all theme hook, preprocess, template, etc. information
- `\Drupal\Core\Utility\ThemeRegistry` – runtime registry cache collector, decorator of `\Drupal\Core\Theme\Registry` and instantiated by `\Drupal\Core\Theme\Registry::getRuntime` creating a theme-specific subset of the registry.
- `\Drupal\Core\Template\Loader\ThemeRegistryLoader` – Twig loader that loads templates based on the `ThemeRegistry` runtime registry.

It's somewhat confusing. But it all revolves around `\Drupal\Core\Theme\Registry`. The `\Drupal\Core\Utility\ThemeRegistry` class is a way to try and optimize caching for the registry data.

## Building the theme registry

The magic happens in `\Drupal\Core\Theme\Registry::build`. The theme registry will first try to retrieve cached registry data about theme information provided by modules, consistent across any theme being used.

```php
if ($cached = $this->cache->get('theme_registry:build:modules')) {
    $cache = $cached->data;
}
```

If that results in a cache miss, Drupal invokes `hook_theme` for all installed modules to create the base theme registry. The `processExtension` method invokes the extensions' `hook_theme` implementation and discovers any preprocess function hooks provided by that extension.

```php
$this->moduleHandler->invokeAllWith('theme', function (callable $callback, string $module) use (&$cache) {
    $this->processExtension($cache, $module, 'module', $module, $this->moduleList->getPath($module));
});
$this->cache->set("theme_registry:build:modules", $cache, Cache::PERMANENT, ['theme_registry']);
```

The next part of building the registry considers the current theme, its theme engine, and base themes. This is where I got confused and a bit lost. The code is calling `processExtension`, again.

```php
foreach (array_reverse($this->theme->getBaseThemeExtensions()) as $base) {
    $base_path = $base->getPath();
    if ($this->theme->getEngine()) {
        $this->processExtension($cache, $this->theme->getEngine(), 'base_theme_engine', $base->getName(), $base_path);
    }
    $this->processExtension($cache, $base->getName(), 'base_theme', $base->getName(), $base_path);
}
```

The theme's engine (Twig) is then processed. Again, it is calling `processExtension`, and I didn't think anything of it.

```php
if ($this->theme->getEngine()) {
    $this->processExtension($cache, $this->theme->getEngine(), 'theme_engine', $this->theme->getName(), $this->theme->getPath());
}
```

Then the theme itself is processed.

```php
$this->processExtension($cache, $this->theme->getName(), 'theme', $this->theme->getName(), $this->theme->getPath());
```

I was perplexed. I know themes do not need to invoke `hook_theme` to provide template overrides. They're automatically detected. Everything I read within `processExtension` for themes was primarily handling their preprocess hooks.

Then I took a step back. I fired up Xdebug and truly stepped through the process. And I realized I overlooked a crucial part of the processing — handling of the `theme_engine`.

## The `twig_theme` hook implementation

Located in the `twig.engine` file is the `twig_theme` theme hook implementation. It gets invoked when `processExtension` is called for `theme_engine` the extension.

```php
function twig_theme($existing, $type, $theme, $path) {
  return drupal_find_theme_templates($existing, '.html.twig', $path);
}
```

The `drupal_find_theme_templates` function lives in `includes/theme.inc`. It scans the directories of all themes available to Drupal and scans their directories for template files based on the given extension, `.html.twig`. The templates are then matched in two ways

- Convert template file names with `-` to `_` for the function naming scheme of theme hooks
- Check if the template file name matches the template file name in an existing theme hook.

If a match exists, the theme registry will use that template file rather than the default template.

Once I discovered this tidbit, I could [support PHPTemplate template overrides with Retrofit](https://github.com/mglaman/retrofit-drupal/commit/742252fa1dcfa12cbf6f14bc54a92495e125cc7a) by overriding logic in `processExtension`.

```php
if ($type === 'theme_engine') {
    $templates = drupal_find_theme_templates($cache, '.tpl.php', $path);
    foreach ($templates as $theme_hook => $info) {
        $cache[$theme_hook]['phptemplate'] = $info['path'] . '/' . $info['template'] . '.tpl.php';
        $cache[$theme_hook]['template'] = 'theme-phptemplate';
        $cache[$theme_hook]['path'] = '@retrofit';
    }
}
```

 

  ![](/themes/custom/mglaman/images/headshot.webp)Thanks for reading

I write about Drupal, PHP, and the quiet infrastructure behind large sites — caching, config, and testing. New posts land every week or two.

 [Subscribe via RSS →](/rss.xml) [About me →](/about-me) 

 



 

  [← Older

Protect your investment in Drupal 7 with Retrofit for Drupal

 ](/blog/protect-your-investment-drupal-7-retrofit-drupal) [Newer →

PhpStorm live templates to automate the boring things

 ](/blog/phpstorm-live-templates-automate-boring-things)