Macros, include and import

Jinja · Practical · 14 min

Repeated HTML snippets like form fields and cards should be written once and called everywhere.

A macro is a template function:

{% macro field(name, label, type="text", value="") %}
  <label>{{ label }} <input name="{{ name }}" type="{{ type }}" value="{{ value }}"></label>
{% endmacro %}

Call it like a function: {{ field("email", "Email", type="email") }}. Macros have default arguments, and extra keyword arguments are available in kwargs if the macro references it.

A {% call %} block passes a chunk of template to a macro, which renders it with {{ caller() }}. That is how you write wrappers such as cards or modals around arbitrary content.

To share macros across files:

  • {% import "forms.html" as forms %} then {{ forms.field(...) }}
  • {% from "forms.html" import field, button as btn %}

Include renders another template in place: {% include "partials/nav.html" %}. ignore missing skips absent files, and a list tries each name in order.

The context rules differ, which trips people up:

  • include passes the current context by default, so the partial sees your variables.
  • import does not pass the context by default (imported templates are cached), so macros in it can't see page variables unless you write import ... with context. Prefer passing values as macro arguments instead.

Guideline: macros for parametrized components, includes for static chunks like footers, inheritance for overall page layout.

Example

from jinja2 import DictLoader, Environment

env = Environment(autoescape=True, trim_blocks=True, lstrip_blocks=True, loader=DictLoader({
    "ui.html": """{% macro field(name, label, type="text") -%}
<label>{{ label }} <input name="{{ name }}" type="{{ type }}"></label>
{%- endmacro %}
{% macro card(title) -%}
<div class="card"><h2>{{ title }}</h2>{{ caller() }}</div>
{%- endmacro %}""",
    "nav.html": "<nav>Hi {{ user }}</nav>",
    "page.html": """{% import "ui.html" as ui %}
{% include "nav.html" %}
{% call ui.card("Sign up") %}{{ ui.field("email", "Email", type="email") }}{% endcall %}""",
}))
print(env.get_template("page.html").render(user="Ann"))
# <nav>Hi Ann</nav><div class="card"><h2>Sign up</h2><label>Email <input name="email" type="email"></label></div>

Remember

  • Macros are template functions with arguments and defaults
  • {% call %} + caller() pass a content block into a macro
  • include sees the current context; import doesn't unless with context

Watch out for

  • Expecting macros from an imported file to see the page's variables.

Check yourself

  1. A macro in macros.html uses {{ user }}. It is imported with {% import "macros.html" as m %}. What does user render as?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: jinja.palletsprojects.com/en/stable/templates/#macros