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:

Drupal’s theme file name suggestions

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.