The Twig processing order in Craft templates

Sometimes the order of things matters. This article begins to explore how Twig Templates in Craft CMS are processed. We'll take a look at the order in which the set, block, extends and include tags get evaluated and rendered. Understanding the logic behind how the page is processed will help you decide when to use these tags; whether you should be using these tags in your layouts, page templates, or partials; and know when you need to consider alternative methods to pass variables between your templates more easily when dealing with more advanced templating needs.

Our example uses a common pattern we see in Craft. Our page load will include a layout template, a page template that extends that layout, and an partial template that is included in that page template. Let's output the full versions of our example templates we'll be working with so we can easily refer back to them once we get into the details. The content of these templates is designed to help us test the order that we see things being output.

Layout Template (_layout.html)
Our Layout Template will not be used directly. We can set variables or fallback values in the Layout Template, but most content that will be rendered in it will be defined when we extend the Layout Template with our Page Template.

<html>
<head>
    <title></title>
</head>
<body>

    {# Set a variable in our layout template #}
    {% set setLayoutContentVariable = 'Set Content from Layout Template' %}

    {# Output block tag content in our layout template #}
    {% block blockContent %}
        <p>Block Content in Layout Template</p>
    {% endblock %}

    {# Output set tag content in our layout template #}
    {% if setLayoutContent is defined %}
        {{ setLayoutContent }}
    {% endif %}

</body>
</html>

Page Template (page.html)
Our Page Template is where most of our content is defined. To reuse content or simplify the complexity of our Page Template, we can include Partial Templates.

{# Extend our layout template #}
{% extends "_layout" %}

{# Set a variable in our page template #}
{% set setPageContentVariable = 'Set Variable in Page Template' %}

{# Output block tag in our page template #}
{% block blockContent %}

    {# Set a variable within our block tag #}
    {% set setBlockContentVariable = 'Set Content from Block Tag in Page Template' %}

    {# Output content in our block tag #}
    <p>Block Content in Page Template</p>

    {# Output a variable from our layout template within our block tag #}
    {% if setLayoutContentVariable is defined %}
        <p>{{ setLayoutContentVariable }}</p>
    {% endif %}

    {# Output a variable from our page template within our block tag #} 
    {% if setPageContentVariable is defined %}
        <p>{{ setPageContentVariable }}</p>
    {% endif %}

    {# Output the content of our parent block tag #}
    {{ parent() }}

    {# Include another template in our block tag #}
    {% include '_partial' with { 
        setLayoutContentVariable : setLayoutContentVariable 
    } %}

{% endblock %}

{# Output set tag in our page template #}
{% set setLayoutContent %}

    {# Output content in our set tag #}
    <p>Set Content in Page Template</p>

    {# Include another template in our set tag #}
    {% include '_partial' %}

{% endset %}

Partial Template (_partial.html)
Our Partial Template can be re-used in multiple places. Most content output within the Partial Template is usually specific to the problem being solved in the Partial Template, but the content within it can originate from several different places.

{# Output content in our include template #}
<p>Content in Partial Template</p>

{# Output a set variable from our layout, that was handed 
   off to our partial template using the `with` statement
   of the include tag #}
{% if setLayoutContentVariable is defined %}
    {{ setLayoutContentVariable }}
{% endif %}

{# Output a set variable from our page where this partial was included #}
{% if setPageContentVariable is defined %}
    {{ setPageContentVariable }}
{% endif %}

{# Output a set variable from the block tag where this partial was included #}
{% if setBlockContentVariable is defined %}
    {{ setBlockContentVariable }}
{% endif %}

Loading our Page Template #

When we load page.html from the above templates, what order do the tags in our three templates _layout, page, and _partial get processed?

The answer is straightforward, but not obvious. Some parts of the page template are processed before the layout template and some are processed after. Some of our variables in the layout and page templates are immediately available to the included partial, other variables are not.

In the following steps, Content refers to any text content being output on the page directly: "Hello World". Twig tags refer to any Twig tags {{ output tags }} or {% action tags %}.

While the processing of our complete Page Template will step back and forth between all of our templates; content and all Twig tags always get processed from top to bottom within the template or template partial (such as an {% include %} template) that is being evaluated. The only exception to this is the {% block %} tag, which gets processed last.

Let's take a look at what order our templates get processed when we load page.html.

  1. Content and Twig Tags (except {% block %}) in our Page Template get processed
  2. Content and Twig Tags (except {% block %}) in our Layout Template get processed
  3. {% block %} tags and the Content and Twig Tags within them in Layout Template get processed

    Available values in Layout Template: The values of all {% set %} tags that were processed in the Page Template are available to the Layout Template as it is processed

    Processing order of {% block %} tags: When a {% block %} tag is processed in the Layout Template, Twig first looks for the overriding {% block %} tag in the Page Template and processes that first. If the {% block %} tag in the Page Template doesn't exist or calls the parent() functions, then (and only then) does the {% block %} tag in the Layout Template get processed.

    Available values in {% block %} tag of Page Template: Within an include tag we immediately have access to variables from the same context in Twig. In our case, the {% block %} tag is in our Page Template so our variables created in the Page Template (setPageContentVariable) and within the {% block %} tag of the Page Template (setBlockContentVariable) are available to use in the Partial Template. If we wish to use a variable in our that was created higher on the page in the Layout Template (such as setLayoutContentVariable) then we have to explictly tell our Partial Template to make it available. We can do so using the include tag's with keyword ({% include ... with ... %}).

  4. All processed content and tags are output to the browser

A few things to note:

Some Twig tags don't output anything. Tags can have varied behaviors. In our example above, the {% extends %} tag tells Twig to continue and process the Layout Template once it's completed processing all of the other tags in the Page Template.

A common practice is to use the {% set %} ... {% endset %} and {% block %} ... {% endblock %} tags to output blocks of content in templates. One reason to chose {% set %} or {% block %} may be because they provide different features. Another reason, which is more clear as we consider the tags in the processing order above, may be because they get processed at different times during the page load.

Visualize the processing of our Page Template #

With those steps laid out. Let's take a closer look at each step and the specific Twig code the step is processing.

1) Content and Twig Tags (except {% block %}) in our Page Template get processed

Page Template (page.html)
Our process kicks off with all items in our Page Template except the {% block %} tags

{% extends "_layout" %}

{% set setPageContentVariable = 'Set Variable in Page Template' %}

...

{% set setLayoutContent %}

    <p>Set Content in Page Template</p>

    {% include '_partial' %}

{% endset %}

Partial Template (_partial.html)
The {% include '_partial' %} tag of the Page Template gets evaluated with the available variables. Note that the setLayoutContentVariable that we output in our {% block %} tag later is not available at this point as we haven't processed anything in the Layout Template yet.

<p>Content in Partial Template</p>

{% if setLayoutContentVariable is defined %}
    {{ setLayoutContentVariable }}
{% endif %}

{% if setPageContentVariable is defined %}
    {{ setPageContentVariable }}
{% endif %}

{% if setBlockContentVariable is defined %}
    {{ setBlockContentVariable }}
{% endif %}

2) Content and Twig Tags (except {% block %}) in our Layout Template get processed

Layout Template (_layout.html)
After the Content and Twig tags in our Page Template are processed, the Content and Twig tags in the Layout Template referenced earlier in our extends tag will get processed.

<html>
<head>
    <title></title>
</head>
<body>

    {% set setLayoutContentVariable = 'Set Content from Layout Template' %}

    ...

    {% if setLayoutContent is defined %}
       {{ setLayoutContent }}
    {% endif %}

</body>
</html>

3) {% block %} tags and the Content and Twig Tags within them in Layout Template get processed

Page Template (page.html)
While we are technically processing the Layout Template at this point, the first tag we find in the Layout Template that has not been processed at this point is the {% block %} tag, and the block tag will first evaluate the overriding {% block %} tag in the Page Template.

{% block blockContent %}

    {% set setBlockContentVariable = 'Set Content from Block Tag in Page Template' %}

    <p>Block Content in Page Template</p>

    {% if setLayoutContentVariable is defined %}
        <p>{{ setLayoutContentVariable }}</p>
    {% endif %}

    {% if setPageContentVariable is defined %}
        <p>{{ setPageContentVariable }}</p>
    {% endif %}

    {{ parent() }}

    {% include '_partial' with { 
        setLayoutContentVariable : setLayoutContentVariable 
    } %}

{% endblock %}

Layout Template (_layout.html)
As we process the {% block %} tag in our Page Template we come across the parent() function which evaluates the content of the {% block %} tag in the parent Layout Template.

{% block blockContent %}
    <p>Block Content in Layout Template</p>
{% endblock %}

Partial Template (_partial.html)
The {% include '_partial' %} tag appears within the {% block %} tag of our Page Template as well. At this point, new variables are available so the output of this template differs from the output we get when this Partial Template earlier when we processed the set tag.

<p>Content in Partial Template</p>

{% if setLayoutContentVariable is defined %}
    {{ setLayoutContentVariable }}<br/>
{% endif %}

{% if setPageContentVariable is defined %}
    {{ setPageContentVariable }}<br/>
{% endif %}

{% if setBlockContentVariable is defined %}
    {{ setBlockContentVariable }}<br/>
{% endif %}

Note: {% block %} tags and {% set %} tags and all of the tags that get output within them can be reordered. So as you review the above, it's important to note at what point in time they are evaluated in the context of the {% extends %} and {% include %} tags. Within the same template multiple {% block %} tags and {% set %} tags can appear an various orders, with various orders of the tags within them, and the specific order of your situation will determine what variables are available and when. It's not as simple as "all {% set %} variables will be available when {% block %} tags are finally processed". That's not true. Some {% set %} variables are more specific than others and will only be available after a {% block %} tag begins to process.

Breaking the order #

As we've explored in the example above, the Twig processing order flows down the page. Variables are processed and become available farther down the page to be output. Sometimes however, we can only determine a value farther down the page, and the value itself needs to be output higher on the page.

Out of the box, some scenarios like this can be handled by Twig but things can get messy quick, and code that once felt dry can begin to feel harder to maintain. Other scenarios simply can't be solved without extending Twig or managing your variable with the help of a plugin.

This article won't go into these situations in a lot of detail, but here are a few ways you can begin to explore if you run into this situation.

  1. Try to set your variables as high on the page as you can. Even arrays of data can be queried and set as variables higher on the page. Once a variable exists, it's much easier to refer to that variable anywhere below it's first instance in your templates.
  2. Create your page variables in a plugin. Craft allows you to set up routes to point to a Controller action instead of a page template.
  3. Consider building a plugin and and managing the variable of the content you need access to in a Twig Global or public variable in the service layer of your plugin.

On the order of things #

This article provides a way to start thinking about the order of events in your Twig Templates. Surely, there are more situations to discuss where the order of things will matter in order to set variables, perform logic, and output content into your templates.

If you'd like to explore the processing order in your templates on your own, you can download the example templates described above for the set and block example along with a utility plugin called Sprout Log. Sprout Log is just a wrapper for the plugin-specific Craft::log() method but can be used in your templates to help determine in what order items are being processed. Logged items will be output in the craft/storage/runtime/sproutlog.log file.

Don't miss any Craft tips, tricks, and community updates