Presentation-oriented Templating Syntax

This post is pretty old, and might contain outdated advice or links. We’re keeping it online, but recommend that you check newer posts to see if there’s a better approach.

All blog posts

POTS is easy to read and write, both for developers and designers

As a web developer you have probably come across Presentation-Oriented Templating Syntax POTS like this:

{% block content %}
  <h1>{{ section.title }}</h1>
  {% for story in stories %}
    <h2><a href="{{ story.url }}">{{ story.title }}</a></h2>
    <p>{{ story.teaser }}</p>
  {% endfor %}
{% end block %}

Without any prior experience of using templating engines, I’m pretty sure it was obvious how it worked. At least you knew what result to expect.

This is by design: the template system is meant to express presentation, not program logic.

Django, a Python framework, first introduced this templating syntax which was later adapted to work with other frameworks and rendering engines like Jinja (Python), Twig (PHP), H2o (PHP), Nunjucks (JavaScript) and Liquid (Ruby). While other different programming languages and frameworks, the templating syntax for each is almost identical. Each supports the same notation for variables, filters, conditionals, inheritance, includes and comments.

This kind of templating engine provides tags which function similarly to some programming constructs – if tag for boolean tests, a for tag for looping, etc.; template inheritance and includes – for managing layouts and components;

In the following moments you will be able to get the key points of this kind of templating and also some small differences between the different languages that use them.

Why POTS is great

  • it has the same semantic and expressive syntax (notation) to bind data to markup for all frameworks using it
  • can be used interchangeably between different frameworks and rendering engines
  • has advanced methods to extend and include other templates
  • is easy to read and write, both for developers and designers

To the advantages pointed before, the best that we could add is that: if you know one, you really know how to work with all of them. Right? Well, while they are pretty much the same, each one has some particular functionality that is usually related to the framework/language they are build on. So what are those little details and why should we care about it?

First of all, because the syntax is similar on different back-ends, we can be sure that we can use it no matter which backend language is used. Secondly, even though they all look pretty much the same, the project can benefit from a specific variation of the syntax. To understand the differences between the variations of templating frameworks using POTS, we will be focusing on variables, filters, tags/conditionals and template inheritance.

Syntax key points

We will be comparing four variations of templating frameworks using POTS:

  • Django ( the ‘original’ Python flavour)
  • Twig ( the PHP version)
  • Nunjucks ( the JavaScript version)
  • Liquid ( the ‘Shopify’ Ruby version)

Since we can use the same data structure, we will use the following as an example:

  "title": "Fishing experiences",
  "stories": [
      "title": "In my own town",
      "teaser": "My journey from noob to fishing expert",
      "url": "/story/2",
      "title": "Meristics",
      "teaser": "Introduction to Meristics basics",
      "url": "/story/3",
{% block content %}
  <h1>{{ section.title }}</h1>
  {% for story in stories %}
    <h2><a href="{{ story.url }}">{{ story.title }}</a></h2>
    <p>{{ story.teaser }}</p>
  {% endfor %}
{% endblock %}

And the output would be:

<h1>Fishing experiences</h1>
<h2><a href="/story/2">In my own town</a></h2>
<p>My journey from noob to fishing expert</p>
<h2><a href="/story/3">Meristics</a></h2>
<p>Introduction to Meristics basics</p>


{{ }}

The application passes variables to the templates. Variables may have attributes or elements on them you can access as well. What a variable looks like heavily depends on the application providing it.

This is the variable lookup order behind the scenes:

  1. check if there is an attribute called 'bar' on foo.
  2. if there is not, check if there is an item 'bar' in foo.
  3. if there is not, return an undefined object.


  1. check if there is an item 'bar' in foo.
  2. if there is not, check if there is an attribute called 'bar' on foo.
  3. if there is not, return an undefined object.

Twig does a slightly different approach:

  1. check if foo is an array and bar a valid element;
  2. if not, and if foo is an object, check that bar is a valid property;
  3. if not, and if foo is an object, check that bar is a valid method (even if bar is the constructor - use __construct() instead);
  4. if not, and if foo is an object, check that getBar is a valid method;
  5. if not, and if foo is an object, check that isBar is a valid method;
  6. if not, return a null value.

foo['bar'] on the other hand only works with PHP arrays:

  1. check if foo is an array and bar a valid element;
  2. if not, return a null value.

It's important to know that the curly braces are not part of the variable, but of the print statement. If you access variables inside tags, don't put the braces around. If a value is undefined or null, nothing is displayed. The same behavior occurs when referencing undefined or null objects. You can also set variables inside the block scope. Twig flavored syntax adds extra functionalities like global variables to help the developer.


You can modify variables for display by using filters.

Filters look like this: {{ name | lower }} . This displays the value of the {{ name }} variable after being filtered through the lower filter, which converts text to lowercase. Use a pipe (|) to apply a filter.

Filters can be “chained.” The output of one filter is applied to the next. {{ text | escape | linebreaks }} is a common idiom for escaping text contents, then converting line breaks to <p> tags.

Some filters take arguments. A filter argument looks like this: {{ bio | truncatewords:30 }}. This will display the first 30 words of the bio variable. Filter arguments that contain spaces must be quoted; for example, to join a list with commas and spaced you’d use {{ list | join:", " }}.

All of the flavored syntaxes have a set of base filters like: capitalize, escape (sometimes shortened to just e), join, last, lenght, lower, random, reverse, slice, sort and float.

But some of them extend their base with:

You can always add your own custom filters.

Conditionals / Tags

Tags look like this: {% tag %}. Tags are more complex than variables: Some create text in the output, some control flow by performing loops or logic, and some load external information into the template to be used by later variables.

Some tags require beginning and ending tags (i.e. {% tag %} ... tag contents ... {% endtag %}). The most common include: if, for, extend, include, macros and comments.


if tests a condition and lets you selectively display content. It behaves exactly as javascript's if behaves.

{% if fishes %}
  We have some {{ fishes }}
{% endif %}


for iterates over arrays and dictionaries.

{% for item in items %}
  {{ item.title }}
{% endfor %}


Macros are comparable with functions in regular programming languages. They are useful to reuse often used HTML fragments to not repeat yourself.

{% macro input(name, value, type) %}
  <input type="{{ type | default("text") }}" name="{{ name }}" value="{{ value | escape }}"/>
{% endmacro %}

And then they could be used like:

  <dt>Fish name</dt>
  <dd>{{ input_field(fish_name) }}</dd>
  <dt>Scientific name</dt>
  <dd>{{ input_field("scientific_name") }}</dd>

One special note about the {% raw %} tag. If you are using a server-side template, to not parse that part of the code as a template.

{% raw %}
  {% if %} this will {{ not be processed }} {% endif %}
{% endraw %}


You can write comments using . Comments are completely stripped out when rendering. Comments are nothing more than a Tag.

{# disabled type of catched fished because of Marine Conservation Institute
  {% for fish in fishes %}
  {% endfor %}

Bonus from specific variations:

  • Nunjucks adds asyncEach and asyncAll for working with asynchronous javascript
  • Liquid adds unless for negation of if statements, and cycle to alternate between different tasks

Template inheritance

Template inheritance is a way to make it easy to reuse templates. When writing a template, you can define "blocks" that child templates can override You start by defining a ‘base’ template:

<!DOCTYPE html>
    {% block head %}
      <link rel="stylesheet" href="style.css" />
      <title>{% block title %}{% endblock %}</title>
    {% endblock %}
    <div id="content">{% block content %}{% endblock %}</div>

And then you extend the base layout, by filling the blocks with content:

{% extends "base.html" %}
{% block title %}Index{% endblock %}
{% block head %}
{% endblock %}
{% block content %}
  <p class="important">Welcome to Fishing experiences.</p>
{% endblock %}

To wrap up

With variables and tags you can simply cycle through any content. Filters let you customize the output given from the data source. Macros help you don’t repeat yourself and with templating inheritance you organize your templates in a structured way.

If you want to know more you can check this references:

Some alternatives:

← All blog posts