For those working with Drupal 8 or 9 theming would know Drupal switched to Twig templates based rendering for various portions of the page. Drupal’s theming engine generates file name suggestions for each portion of the page as you can see in screenshot below:
While working on new home page for a project Merit 500 here at Imbibe today, I had a peculiar requirement where I needed to override the body
field of the home page and write a twig template for the node body. The same was necessitated because of the requirement of injecting some content dynamically from the database into the node’s body
. Drupal out of the box does not support shortcodes for injecting dynamic blocks of content into a page / post’s html like WordPress does, and I did not wanted to use a custom third party plugin for the purpose.
Notice, I am talking of overriding the body
of the home page node only. Drupal’s default file name suggestions for field body
of a node comprise the following:
<!-- FILE NAME SUGGESTIONS:
* field--node--body--page.html.twig
* field--node--body.html.twig
* field--node--page.html.twig
* field--body.html.twig
* field--text-with-summary.html.twig
x field.html.twig
-->
The problem is none of them is specific to a single node. All of them would impact multiple nodes or potentially all entities where the body
field is used across the Drupal installation depending upon the file name chosen. I just needed to override the body
for the node used as my Drupal installation’s home page only.
Quick googling did not turn out anything out of the box, but enough hints to come up with a custom theme name suggestion using the following hook in my theme’s .theme
file:
function theme_name_theme_suggestions_field_alter(array &$suggestions, array $variables) {
if ($node = \Drupal::routeMatch()->getParameter('node')) {
if (in_array('field__body', $suggestions)) {
$suggestions[] = 'field__node__' . $node->id() . '__body';
}
}
}
Basically you use the theme_suggestions_field_alter
hook to check whether the route matches a node route and if the hook has been invoked for the body
field. And if yes, you append the current node’s id to provide a new theme file name suggestion.
And tada… Drupal now exposed a node specific file name suggestion for the body field as you can see below:
<!-- FILE NAME SUGGESTIONS:
x field--node--7--body.html.twig
* field--node--body--page.html.twig
* field--node--body.html.twig
* field--node--page.html.twig
* field--body.html.twig
* field--text-with-summary.html.twig
* field.html.twig
-->
Not bad, right? 🙂
If you check Merit 500’s home page, the section “The Top 5 Rankings
” is generated dynamically using the above trick for overriding the body
for a specific Drupal node in a Twig template.
Having said this, yes there were other potential approaches. For example, the different sections above and below the content that needed to be dynamic could have been converted to custom blocks and a block could have been provided in a module for outputting the dynamic content. It was too much of a hassle in our use case and the node instance specific field template seemed much easier and maintainable.
Also you could have overridden the node template itself, e.g. by providing node--7--full.html.twig
template. However it would override the complete node including other fields too (and not only the body field). Although the same was not a concern for us as the node contained body field only, we still could not opt for a node level template as our theme css relied on some wrapper html tags (e.g. <article> and <div> etc) generated by Drupal’s default node templates. And hard-coding them into our own node twig template was not perferable.
Also, you might want to take a close look at the hook above in case multiple entity bundles use the body field having field__body
machine name. The hook above in that case might lead to wrong caching of template suggestions depending upon which entity bundle instance is viewed first immediately after clearing Drupal caches. We did not had any other entity bundles using body field and hence the above code worked just fine.
Lastly you might want to further customise the template suggestion based on view mode (e.g. full, teaser etc). The same could be easily done by using the options available in the $suggestions
parameter passed to your theme suggestion function. Again we did not need that specificity and hence did not incorporate it.
Happy Drupaling!!!
UPDATE (Aug 11, 2020):
As suspected above, we ourselves faced the issue with this theme function when I had a custom block with body
field on the same page as a node whose body
was being overridden. The above implementation caused mix-ups with rendering of block body
field also. After more experimentation, I came up with the following update to theme function which seems to work fine now.
function themename_theme_suggestions_field_alter(array &$suggestions, array $variables) {
$object = $variables['element']['#object'];
if (get_class($object) == 'Drupal\node\Entity\Node') {
if (in_array('field__body', $suggestions)) {
$suggestions[] = 'field__node__' . $object->id() . '__body';
}
}
}
Instead of relying on the Http context information (\Drupal::routeMatch()->getParameter('node')
) which is going to be the same for all entities rendered on a page, we switched to using the explicitly passed entity which ensures the theme suggestion is only added for Node entities and not other entity bundles.
Hi Rahul,
Nice post, I have one doubt over here, lets say currently drupal gives node–7–full.html.twig, in this case the problem i am facing is that when the node id of the desired page template is different for dev & prod environment this fails, is there any way we can use url alias to provide them suggestions.
Well, I have faced similar issues too Tejas. I / my team had to tweak node ids between dev and prod. We did not use url alias and remained with node ids as url aliases were finalised by Content team while going to prod and were subject to change down the line.
But it shouldn’t be too difficult what you are trying to achieve. You already have the node id in the code posted above. You can simply fetch the url alias based on node id, and construct a theme hook suggestion based on the same.
Keep in mind though to filter out characters not allowed by file-systems in the file name (e.g. a forward slash(/)).
The table name is path_alias where you can locate the url for a node based on node id.