Extensions let you add syntax such as caching or i18n blocks when filters and macros aren't enough.
Filters, tests and globals extend what expressions can do. Extensions go further and add new tags. Built-in ones are enabled by name:
Environment(extensions=["jinja2.ext.i18n", "jinja2.ext.loopcontrols", "jinja2.ext.do"])i18n adds {% trans %} blocks for gettext, loopcontrols adds break and continue, do runs an expression without output, and debug adds {% debug %} to dump the context.
A custom extension subclasses jinja2.ext.Extension, declares tags = {"mytag"} and implements parse(self, parser). The parser hands you the token stream; you consume your tag, parse arguments with parser.parse_expression(), collect the body with parser.parse_statements(("name:endmytag",), drop_needle=True), and return AST nodes. The most common pattern returns a nodes.CallBlock that calls a method on the extension via self.call_method("_render", args); that method receives caller, a function that renders the body. This is how fragment caching extensions work: compute a key, return cached output or call caller() and store it.
Extensions can also add Environment attributes with self.environment.extend(...) and preprocess source or filter the token stream.
Async rendering. Environment(enable_async=True) compiles templates so they can await async functions and iterate async generators passed in the context. Render with await template.render_async(...). It is useful when templates pull data from async sources, but it is slower than sync rendering, so prefer loading data first and rendering synchronously.
Example
import asyncio
from jinja2 import Environment, nodes
from jinja2.ext import Extension
class ShoutExtension(Extension):
tags = {"shout"}
def parse(self, parser):
lineno = next(parser.stream).lineno # consume the "shout" token
times = parser.parse_expression() # {% shout 2 %}
body = parser.parse_statements(("name:endshout",), drop_needle=True)
return nodes.CallBlock(self.call_method("_render", [times]), [], [], body).set_lineno(lineno)
def _render(self, times, caller):
return (caller().strip().upper() + "! ") * times
env = Environment(extensions=[ShoutExtension, "jinja2.ext.loopcontrols"])
print(env.from_string("{% shout 2 %}hi {{ name }}{% endshout %}").render(name="ann"))
# HI ANN! HI ANN!
print(env.from_string("{% for i in range(9) %}{% if i > 2 %}{% break %}{% endif %}{{ i }}{% endfor %}").render())
# 012
async def get_user():
return "Bo"
aenv = Environment(enable_async=True)
print(asyncio.run(aenv.from_string("Hello {{ get_user() }}").render_async(get_user=get_user)))
# Hello BoRemember
- Built-in extensions: i18n, loopcontrols, do, debug
- A custom tag = Extension subclass with tags and parse() returning AST nodes
- CallBlock + call_method lets Python code wrap a rendered body
- enable_async + render_async await async functions in templates
Watch out for
- Writing an extension for something a filter, global or macro could do more simply.