The Power of Extending Twig Templates

The Power of Extending Twig Templates

default avatar
Pensé parDavid Hernandez
Juillet 21, 2016
Twig Templates

Extending in Twig is a method by which one template can inherit content from another template, while still being able to override parts of that content. This relationship is easy to imagine if you are familiar with Drupal’s default system of template inheritance.

A theme can have multiple page templates, many node templates, even more field templates, and a plethora of block and Views template. And it is common for those templates to largely be identical, save for a snippet of markup or some logic. The advantage in extending templates is reducing this duplication, thereby simplifying architecture and easing maintenance.

Let’s say, for example, you want to change the template for a specific block, adding a wrapper div around the main content area. This might be done by copying the standard block template and giving it a name specific to your block.

Classy’s block.html.twig template
{%
  set classes = [
    'block',
    'block-' ~ configuration.provider|clean_class,
    'block-' ~ plugin_id|clean_class,
  ]
%}
<div{{ attributes.addClass(classes) }}>
  {{ title_prefix }}
  {% if label %}
    <h2{{ title_attributes }}>{{ label }}</h2>
  {% endif %}
  {{ title_suffix }}
  {% block content %}
    {{ content }}
  {% endblock %}
</div>

Copied to block--my-special-block.html.twig
{%
  set classes = [
    'block',
    'block-' ~ configuration.provider|clean_class,
    'block-' ~ plugin_id|clean_class,
  ]
%}
<div{{ attributes.addClass(classes) }}>
  {{ title_prefix }}
  {% if label %}
    <h2{{ title_attributes }}>{{ label }}</h2>
  {% endif %}
  {{ title_suffix }}
  {% block content %}
    <div class=”content-wrapper”>{{ content }}</div>
  {% endblock %}
</div>

This accomplishes your goal. You have a template specific to this particular block, and a wrapper div just where you need it. Following the same method, and with a complex site, you can end up with lots of different block templates (or node templates, or field templates, or … you get the idea.)

But, now you have a different problem. The majority of the template is duplicated. All the CSS classes, the outer wrapper, the markup for the block title, etc. If any of that needs to be changed, like changing all block titles from H2s to H3s, you have to update every single one of those templates.

Even if this happens infrequently enough not to be considered time consuming, it is still prone to errors. You might make a mistake in one template, miss one that needs changing, or even change one that should not be changed.

This is where {% extends %} comes in

Extending templates allows you to reference the original template, and only override the parts that are unique to the child template.

In the block example, we can create a block--my-special-block.html.twig template with this content:

{% extends "block.html.twig" %}
{% block content %}
  <div class=”content-wrapper”>{{ parent() }}</div>
{% endblock %}

That’s it. That is the whole template. Twig uses the original block.html.twig template as the main template, and only uses what we override in the more specific block--my-special-block.html.twig template.

The parent() function simply returns all of the content within the {% block %} tags in the original template. This saves us from having to duplicate that content; keeping the template simple, and future proofing it. If any of that content changes in the original template, we don’t have to update the block--my-special-block.html.twig template.

In this example, the content in the original template is fairly simple, only printing the content variable, but imagine if there was a large amount of multiline html and Twig code wrapped in those block tags.

Twig blocks, not Drupal blocks!

This overriding is done by using Twig blocks. (Terminology is fun!) The Twig block is what you see identified by the {% block %} and {% endblock %} tags. The word "content" is the identifier for the block. You can have multiple blocks in a single template.

In the block--my-special-block.html.twig template file, we can do anything we want inside the block tags. Twig will replace the original templates “block” with the one in block--my-special-block.html.twig.

What else?

Well, you have access to pretty much everything in the main template, except the printed markup. So, for example, you can modify the variables it uses.

{% extends "block.html.twig" %}
{% set attributes = attributes.addClass(‘super-special’) %}

This template will add a CSS class called "super-special" to the attributes printed in the outer wrapper of the original block template. The alternative would be to copy the content of the entire block.html.twig template just to add this class to the ‘classes’ array at the top of the file.

You can also just set a variable that will be used by the original template.

{% extends "block.html.twig" %}
{% set foo = 'yellow' %}

Imagine a series of variant field or content type templates that set variables used by the original template for classes, determining structure, etc.

You can even add Twig logic.

{% extends "block.html.twig" %}
{% block content %}
  {% if foo %}
    <div class=”content-wrapper”>{{ parent() }}</div>
  {% else %}
    {{ parent() }}
  {% endif %}
{% endblock %}

Pretty much anything you still might want to do with Twig, inside or outside of the block tags, you can still do.

Things to note

Before you jump right in, and bang your head against a wall trying to figure out why something isn’t working, there a few things to know.

  • The {% extends %} line needs to be at the top of the file.
  • When overriding markup, you can only change what is within block tags in the original template. So add {% block %} tags around anything you might want to modify.
  • You cannot print additional things outside of the overriding block tags. You will have an extends line. You can set variables, add comments, add logic, and override blocks. You cannot put other pieces of markup in the template. Only markup that is inside a block.
  • If Drupal does not extend the correct template, based on what you expect from template inheritance, you may have to explicitly state the template you want.
    Example: {% extends "@classy/block/block.html.twig" %}

Additional Resources