Andrew Welch · Insights · #craftcms #twig #frontend

Published , updated · 5 min read ·


Please consider 🎗 sponsoring me 🎗 to keep writing articles like this.

Twig Processing Order & Scope

The Twig tem­plat­ing lan­guage has a lit­tle-known pro­cess­ing order & scope that is impor­tant to understand

Twig is a fan­tas­tic fron­tend tem­plat­ing lan­guage from Sen­sio Labs that’s gain­ing wide­spread adop­tion. My first expo­sure to it was in Craft CMS, but it’s being used in a num­ber of CMS’s & frame­works such as Sym­fony, Grav, Dru­pal 8, Octo­ber CMS, Bolt, etc. It can even be used in Word­Press via Tim­ber. This wide­spread use makes it a valu­able skill to master.

The rea­son Twig is gain­ing wide­spread adop­tion is that it com­bines ease of use with flex­i­bil­i­ty and pow­er. Fron­tend devel­op­ers who are com­fort­able writ­ing HTML and maybe a lit­tle JavaScript can pick up Twig very quickly.

For the most part, Twig is pret­ty straight­for­ward to learn and imple­ment. I’ll assume that you’re famil­iar with Twig; if you’re not, check out the Twig Book (skip ahead to Chap­ter 3: Twig for Tem­plate Designers).

There are, how­ev­er, two non-obvi­ous aspects of Twig: pro­cess­ing order and scope.

Link Twig Processing Order

I cre­at­ed a handy info­graph­ic on Twig Pro­cess­ing Order a bit ago, and will be div­ing in a bit deep­er here.

Twig Pro­cess­ing Order infographic

Here are the code exam­ples used in the above infographic:

{{ foo }}
{% set foo = 'ruff' %}
{% include '_include' %}

{% block content %}
{% endblock %}

{% block notoverridden %}
	{{ foo }}
{% endblock %}
{% extends '_layout' %}

{% set foo = 'bar' %}

{% block content %}
	{{ foo }}
{% endblock %}
{% set len = foo |length %}
{% set foo = 'woof' %}
{{ foo }}

This is a pret­ty typ­i­cal tem­plate set­up in Twig. We have a _layout.twig that includes _include.twig, and a page.twig that extends our _layout.twig template.

The inter­est­ing part here is the order in which these things are processed:

  1. The code in _page.twig that is not in a {% block %}
  2. The code in _layout.twig that is not in a {% block %}
  3. The code that is includ­ed in _include.twig
  4. The {% block %} code in _page.twig
  5. The {% block %} code in _layout.twig

Now, you may say Okay, that’s great, but why does it mat­ter?” Usu­al­ly, it does­n’t. But there are sit­u­a­tions that will leave you scratch­ing your head try­ing to under­stand what’s going on if you don’t under­stand this Twig pro­cess­ing order.

The exam­ple code above would out­put the following:

bar
woof
ruff
ruff

Is that what you expect­ed? While it may seem arbi­trary, the pro­cess­ing order is actu­al­ly done this way for a very impor­tant rea­son: we need to be able to over­ride things in our tem­plates that extend oth­er templates.

Link Twig Scope

Hey, wait a minute… did­n’t we change foo to 'woof' via {% set foo = 'woof' %} in the _include.twig tem­plate? Why was that nev­er out­put? Let me intro­duce you to the con­cept of scope.

Vari­ables in Twig exist only in the con­text in which they are defined. Sub­sti­tute tem­plate’ for con­text’, and you’ll prob­a­bly under­stand it a bit bet­ter. In oth­er words, if we define a vari­able in one of our tem­plates, it exists only there.

For exam­ple, if in our _include.twig tem­plate we did {% set woof = 'bark' %}, the vari­able woof will not be avail­able in our page.twig or our _layout.twig tem­plates. It’s only avail­able for use in the _include.twig tem­plate where we defined it.

Aha, but then how are we access­ing the vari­able foo (defined in our page.twig tem­plate) from the _include.twig tem­plate? The answer is that Twig pass­es down a copy of the cur­rent con­text to tem­plates that we include.

Copy is the key word here. While we can access foo in _include.twig, any changes we make to it will not be prop­a­gat­ed back up to page.twig or _layout.twig. That’s why we nev­er saw woof get out­put anywhere.

And indeed, when we include tem­plates in Twig, we can add vari­ables to the Twig con­text it will inher­it like this {% include '_include' with { 'someVar': 'someValue' } %}. We can even say we don’t want to include any of the par­ent con­text (only the val­ues we pass in) via {% include '_include' with { 'someVar': 'someValue' } only %}. Check out the Twig docs for details.

For­tu­nate­ly, tem­plates that extend oth­er tem­plates share the same con­text, so vari­ables are both acces­si­ble & change­able in this case. 

Link Wrapping Up

That’s it. Twig pro­cess­ing order and scope may seem a bit eso­teric, but under­stand­ing how your tools work under the hood will save you many hours of frus­tra­tion try­ing to fig­ure out why things are not work­ing as expected.

Hap­py templating!