Extra Tags
This page documents extra tags that are not included in standard Liquid. See the tag reference for details of all standard tags. Each tag described here must be registered with a liquid.Environment
to make it available to templates rendered from that environment.
extends / block
New in version 1.8.0
{% extends "<string>" %}
{% block <identifier,string> [, required] %}
<literal,statement,tag> ...
{% endblock [<identifier,string>] %}
The {% extends %}
and {% block %}
tags add template inheritance features to Python Liquid. In this example page.html
inherits from base.html
and overrides the content
block. As page.html
does not define a footer
block, the footer from base.html
is used.
from liquid import DictLoader
from liquid import Environment
from liquid.extra import add_inheritance_tags
loader = DictLoader(
{
"base.html": (
"<body>\n"
' <div id="content">{% block content required %}{% endblock %}</div>\n'
' <div id="footer">{% block footer %}Default footer{% endblock %}</div>\n'
"</body>"
),
"page.html": (
"{% extends 'base.html' %}\n"
"{% block content %}Hello, {{ you }}!{% endblock %}"
),
}
)
env = Environment(loader=loader)
add_inheritance_tags(env)
template = env.get_template("page.html")
print(template.render(you="World"))
<body>
<div id="content">Hello, World!</div>
<div id="footer">Default footer</div>
</body>
A template can contain at most one {% extends %}
tag, and that tag should normally be the first in the template. All other template text and tags (including whitespace) preceding {% extends %}
will be output normally. Subsequent template text and tags outside any {% block %}
tags will be ignored, unless rendering a base template directly.
As soon as an {% extends %}
tag is found, template rendering stops and Python Liquid loads the parent template (using the configured loader) before searching for {% block %}
tags. We keep loading and searching up the inheritance chain until a parent template with no {% extends %}
tag is found, this is the base template.
The base template is then rendered, substituting its blocks with those defined in its children.
Block Names
Every {% block %}
must have a name and that name must be unique within a single template. Block names must be valid Liquid identifiers, optionally enclosed in quotes (quoted and unquoted block names are equivalent).
{% endblock %}
tags can include a name too. If given a name and that name does not match the one given at the start of the block, a TemplateInheritanceError
is raised when parsing the template.
<body>
<div id="content">
{% block content %}
{% block title %}
<h1>Some Title</h1>
{% endblock title %}
{% endblock content %}
</div>
<div id="footer">
{% block footer %}
Default footer
{% endblock footer %}
</div>
</body>
Block Scope
All blocks are scoped. Variables defined in base templates and enclosing blocks will be in scope when rendering overridden blocks.
{% assign thing = 'item' %}
{% for i in (1..3) %}
{% block list-item %}{% endblock %}
{% endfor %}
{% extends "base" %}
{% block list-item %}
{{ thing }} #{{ i }}
{% endblock %}
item #1
item #2
item #3
Variables defined in an overridden block will go out of scope after that block has been rendered.
{% assign greeting = "Hello" %}
{% block say-hi %}{{ greeting }}, World!{% endblock %}
{{ greeting }}, World!
{% extends "base" %}
{% block say-hi %}
{% assign greeting = "Goodbye" %}
{{ greeting }}, World!
{{ block.super }}
{% endblock %}
Goodbye, World!
Hello, World!
Hello, World!
Required Blocks
Use the {% block %}
tag's required
argument to indicate that the block must be overridden by a child template. If a required block does not get implemented by a child template, a RequiredBlockError
exception is raised at render time.
In this example, if the template were to be rendered directly, we would expect a RequiredBlockError
due to the content
block being required.
<head>
{% block head %}{% endblock %}
<head>
<body>
<div id="content">{% block content required %}{% endblock %}</div>
<div id="footer">{% block footer %}Default footer{% endblock %}</div>
</body>
Super Blocks
A block
object is available inside every {% block %}
tag. It has just one property, super
. If a {% block %}
is overriding a parent block, {{ block.super }}
will render the parent's implementation of that block.
In this example we use {{ block.super }}
in the footer
block to output the base template's footer with a year appended to it.
<head>
{% block head %}{% endblock %}
<head>
<body>
<div id="content">{% block content required %}{% endblock %}</div>
<div id="footer">{% block footer %}Default footer{% endblock %}</div>
</body>
{% extends "base" %}
{% block content %}Hello, World!{% endblock %}
{% block footer %}{{ block.super }} - 2023{% endblock %}
<body>
<div id="content">Hello, World!</div>
<div id="footer">Default footer - 2023</div>
</body>
if (not)
New in version 1.5.0
A drop-in replacement for the standard if
tag that supports a logical not
operator and grouping terms with parentheses. See the tag reference for a description of standard if
expressions.
In this example, {% if not user %}
is equivalent to {% unless user %}
, however, not
can also be used after and
and or
, like {% if user.active and not user.title %}
, potentially saving nested if
and unless
tags.
from liquid import Environment
from liquid.extra.tags import IfNotTag
env = Environment()
env.add_tag(IfNotTag)
template = env.from_string("""\
{% if not user %}
please log in
{% else %}
hello user
{% endif %}
""")
data = {
"user": {
"eligible": False,
"score": 5
}
}
print(template.render(**data))
hello user
The not
prefix operator uses Liquid truthiness. Only false
and nil
are not truthy. Empty strings, arrays and objects all evaluate to true
.
Parentheses
and
and or
operators in Liquid are right associative. Where true and false and false or true
is equivalent to (true and (false and (false or true)))
, evaluating to false
. Python, on the other hand, would parse the same expression as (((true and false) and false) or true)
, evaluating to true
.
This implementation of if
maintains that right associativity so that any standard if
expression will behave the same, with or without non-standard if
. Only when not
or parentheses are used will behavior deviate from the standard.
{
"user": {
"eligible": false,
"score": 5
},
"exempt": true
}
{% if (user != empty and user.eligible and user.score > 100) or exempt %}
user is special
{% else %}
denied
{% endif %}
user is special
{% if user != empty and user.eligible and user.score > 100 or exempt %}
user is special
{% else %}
denied
{% endif %}
denied
inline if / else
New in version 1.5.0
Drop-in replacements for the standard output statement, assign
tag, and echo
tag that support inline if
/else
expressions. You can find a BNF-like description of the inline conditional expression in this gist.
Inline if
/else
expressions are designed to be backwards compatible with standard filtered expressions. As long as there are no template variables called if
or else
within a filtered expression, standard output statements, assign
tags and echo
tags will behave the same.
from liquid import Environment
from liquid.extra import add_inline_expression_tags
env = Environment()
add_inline_expression_tags(env)
template = env.from_string("{{ 'hello user' if user.logged_in else 'please log in' }}")
data = {
"user": {
"logged_in": False
}
}
print(template.render(**data))
please log in
The else
part of an inline expression is optional, defaulting to undefined.
{{ 'hello user' if user.logged_in }}!
!
Inline conditional expressions are evaluated lazily. If the condition is falsy, the leading object is not evaluated. Equally, if the condition is truthy, any expression following else
will not be evaluated.
With Filters
The inline conditional expressions added to Python Liquid 1.5.0 differs slightly from those found in Python Liquid Extra. Previously, trailing filters would be applied regardless of which branch of the condition was taken. Now, "tail filters" are distinguished from alternative branch filters with a double pipe token (||
). See examples below.
Filters can appear before an inline if
expression.
{{ 'hello user' | capitalize if user.logged_in else 'please log in' }}
Or after an inline if
expression. In which case filters will only be applied to the else
clause.
{% assign param = 'hello user' if user.logged_in else 'please log in' | url_encode %}
Or both.
{{% assign param = 'hello user' | capitalize if user.logged_in else 'please log in' | url_encode %}
Use a double pipe (||
) to start any filters you want to apply regardless of which branch is taken. Subsequent "tail filters" should be separated by a single pipe (|
).
{{% assign name =
user.nickname | downcase
if user.has_nickname
else user.last_name | capitalize
|| prepend: user.title | strip
%}
macro / call
New in version 1.5.0
{% macro <string> [[,] [ <object>, ... ] [ <identifier>: <object>, ... ]] %}
{% call <string> [[,] [ <object>, ... ] [ <identifier>: <object>, ... ]] %}
Define parameterized Liquid snippets using the macro
tag, and call them using the call
tag.
Using the macro
tag is like defining a function. Its parameter list defines arguments, possibly with default values. A macro
tag's block has its own scope including its arguments and template global variables, just like the render
tag.
Note that argument defaults are bound late. They are evaluated when a call
expression is evaluated, not when the macro is defined.
from liquid import Environment
from liquid import StrictUndefined
from liquid.extra import add_macro_tags
env = Environment(undefined=StrictUndefined)
add_macro_tags(env)
template = env.from_string("""\
{% macro 'price' product, on_sale: false %}
<div class="price-wrapper">
{% if on_sale %}
<p>Was {{ product.regular_price | prepend: '$' }}</p>
<p>Now {{ product.price | prepend: '$' }}</p>
{% else %}
<p>{{ product.price | prepend: '$' }}</p>
{% endif %}
</div>
{% endmacro %}
{% call 'price' products[0], on_sale: true %}
{% call 'price' products[1] %}
""")
data = {
"products": [
{
"title": "Some Shoes",
"regular_price": "5.99",
"price": "4.99"
},
{
"title": "A Hat",
"regular_price": "16.00",
"price": "12.00"
}
]
}
print(template.render(**data))
<div class="price-wrapper">
<p>Was $5.99</p>
<p>Now $4.99</p>
</div>
<div class="price-wrapper">
<p>$12.00</p>
</div>
Excess Arguments
Excess arguments passed to call
are collected into args
and kwargs
.
{% macro 'foo' %}
{% for arg in args %}
- {{ arg }}
{% endfor %}
{% for arg in kwargs %}
- {{ arg.0 }} => {{ arg.1 }}
{% endfor %}
{% endmacro %}
{% call 'foo' 42, 43, 99, a: 3.14, b: 2.71828 %}
- 42
- 43
- 99
- a => 3.14
- b => 2.71828
with
{% with <identifier>: <object> [, <identifier>: object ... ] %}
<literal,statement,tag> ...
{% endwith %}
Extend the local namespace with block scoped variables.
Register WithTag
with a liquid.Environment
to make with
available to templates rendered from that environment.
from liquid import Environment
from liquid.extra.tags import WithTag
env = Environment()
env.add_tag(WithTag)
{ "collection": { "products": [{ "title": "A Shoe" }] } }
{% with p: collection.products.first %}
{{ p.title }}
{% endwith %}
{{ p.title }}
{% with a: 1, b: 3.4 %}
{{ a }} + {{ b }} = {{ a | plus: b }}
{% endwith %}
A Shoe
1 + 3.4 = 4.4