Skip to content

Tags

Tags control template logic and structure. They use {% %} syntax.

All tag arguments support filter expressions and filter chaining. For example:

{% set x = "  hello  "|trim|upper %}

{% with greeting="hello"|upper %}

{% for i in "5,1,2"|split:","|sort %}

{% cycle "a"|upper "b"|upper %}

{% firstof "" "world"|upper %}

{% ifchanged "hello"|upper %}

Table of Contents


Control Flow

if / elif / else / endif

Conditional rendering with a wide range of operators.

{% if user.is_authenticated %}

  <p>Welcome back, {{ user.name }}!</p>

{% else %}

  <p>Please <a href="/login">log in</a>.</p>

{% endif %}
{% if user.role == 'admin' %}

  <p>Admin panel</p>

{% elif user.is_staff %}

  <p>Staff dashboard</p>

{% else %}

  <p>Guest view</p>

{% endif %}
{% if (user.role == 'admin' or user.is_staff) and user.is_active %}

  <p>Active staff member</p>

{% endif %}



{% if item not in cart_items %}

  <button>Add to cart</button>

{% endif %}

Supported operators: ==, !=, <, <=, >, >=, in, not in, and, or, not

Operator precedence (highest to lowest): comparison → and → or

for / empty / endfor

Loop over arrays and objects. Injects forloop meta tracking.

<ul>

{% for item in items %}

  <li>{{ forloop.counter }}: {{ item }}</li>

{% empty %}

  <li>No items found</li>

{% endfor %}

</ul>
{% for group in items|regroup:"category" %}

  <h3>{{ group.grouper }}</h3>

  {% for item in group.list %}- {{ item.name }}

  {% endfor %}

{% endfor %}
{% for i in "5,1,2,3,4"|split:","|sort|join:"," %}

  {{ i }}

{% endfor %}
{% for key, value in config %}

  <dt>{{ key }}</dt>

  <dd>{{ value }}</dd>

{% endfor %}
{% for department in departments %}

  <h2>{{ department.name }}</h2>

  {% for employee in department.employees %}

    <span>#{{ forloop.parentloop.counter }}.{{ forloop.counter }} {{ employee.name }}</span>

  {% endfor %}

{% endfor %}

forloop Metadata

| Variable | Description |

|----------|-------------|

| forloop.counter | 1-based index |

| forloop.counter0 | 0-based index |

| forloop.revcounter | Reverse 1-based index |

| forloop.revcounter0 | Reverse 0-based index |

| forloop.first | true on first iteration |

| forloop.last | true on last iteration |

| forloop.parentloop | Parent loop context (nested loops) |

Real-world table with alternating row classes:

<table>

{% for row in rows %}

  <tr class="{% cycle 'row-odd' 'row-even' %}">

    <td>{{ row.name }}</td>

    <td>{{ row.value }}</td>

  </tr>

{% endfor %}

</table>

with / endwith

Scope localized variables.

{% with user.profile.address as addr %}

  <p>{{ addr.city }}, {{ addr.zip }}</p>

{% endwith %}
{% with greeting="Hello", who="World" %}

  <p>{{ greeting }} {{ who }}</p>

{% endwith %}
{% with a=5, b=10 as total %}

  <p>Total: {{ total }}</p>

{% endwith %}
{% with greeting="hello"|upper, who="world"|upper %}

  <p>{{ greeting }} {{ who }}</p>

{% endwith %}

cycle

Cycle through values sequentially.

{% for row in rows %}

  <tr class="{% cycle 'row-odd' 'row-even' %}">...</tr>

{% endfor %}
{% cycle 'row-odd' 'row-even' as row_class %}

<tr class="{{ row_class }}">
{% for item in items %}

  {% cycle 'a' 'b' 'c' as marker silent %}

  {% if marker == 'b' %}

    <strong>{{ item }}</strong>

  {% else %}

    {{ item }}

  {% endif %}

{% endfor %}
{% cycle "hello"|upper "world"|upper %}

firstof

Return the first truthy value.

{% firstof user.display_name user.username "Anonymous" %}
{% firstof "" "world"|upper %}

Variable Assignment

set

Assign a value to a variable.

{% set total = price * quantity %}

<p>Total: ${{ total|floatformat:2 }}</p>
{% set greeting %}

  Hello {{ user.name|title }}, welcome to {{ site.name }}!

{% endset %}



<h1>{{ greeting|safe }}</h1>
{% set tax_rate = 0.08, tax = subtotal|mult:tax_rate %}
{% set greeting = "hello"|upper %}

<p>{{ greeting }}</p>

{% set cleaned = "  hello  "|trim|upper %}

<p>{{ cleaned }}</p>

Variables set with {% set %} persist in the current scope and can be used after the tag.


Change Detection

ifchanged / endifchanged

Render the body only when a value changes.

{% for item in changelog %}

  {% ifchanged item.timestamp %}

    <h3>{{ item.timestamp|date:"Y-m-d" }}</h3>

  {% endifchanged %}

  <p>{{ item.change }}</p>

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

  {% ifchanged item.category %}

    <h2>{{ item.category }}</h2>

  {% else %}

    <p>Same category as above</p>

  {% endifchanged %}

{% endfor %}
{% ifchanged "hello"|upper %}

  <p>The value changed</p>

{% endifchanged %}

Date and Time

now

Output the current date/time.

<p>Current time: {% now "Y-m-d H:i:s" %}</p>

<p>Pretty date: {% now "F j, Y" %}</p>

Uses the same format codes as the date filter (Django-style tokens like Y, m, d, H, i, s, F).

{% now "Y-m-d"|upper %}

The filter is applied to the format string, not the rendered date. To transform the formatted output, use {% set x = "now"|date:"Y-m-d"|upper %}{{ x }} instead.

Real-world copyright footer:

<footer>

  &copy; {{ "now"|date:"Y" }} {{ site.name }}. All rights reserved.

</footer>

Utility Tags

static

Generate a static file URL.

<link rel="stylesheet" href="{% static "css/main.css" %}">

<script src="{% static "js/app.js" %}"></script>

<img src="{% static "images/logo.svg" }}" alt="{{ site.name }}">

Configure the prefix at compile time:

const { compile } = require('miki-template');

const template = compile(source, { staticUrl: '/assets/' });
import { compile } from 'miki-template';

const template = compile(source, { staticUrl: '/assets/' });

url

Build a URL from a route name.

<a href="{% url 'user.profile' user.id %}">Profile</a>
{% url 'posts.show' post.id tab='comments' %}
{% url 'user.profile.posts.show' user.id post.id %}

Configure with a custom resolver:

const { compile } = require('miki-template');

const template = compile(source, {

  urlHelper: (routeName, ...args) => {

    // Convert "user.profile" + [42] → "/user/profile/42"

    return '/' + routeName.split('.').join('/') + '/' + args.join('/');

  }

});
import { compile } from 'miki-template';

const template = compile(source, {

  urlHelper: (routeName, ...args) => {

    return '/' + routeName.split('.').join('/') + '/' + args.join('/');

  }

});

regroup

Group a list by a common attribute.

{% regroup people by gender as departments %}

{% for dept in departments %}

  <h3>{{ dept.grouper }}</h3>

  {% for person in dept.list %}

    <p>{{ person.name }}</p>

  {% endfor %}

{% endfor %}

You can also use regroup as a filter inside a {% for %} loop:

{% for group in items|regroup:"category" %}

  <h3>{{ group.grouper }}</h3>

  {% for item in group.list %}

    <p>{{ item.name }}</p>

  {% endfor %}

{% endfor %}

spaceless

Remove whitespace between HTML tags.

{% spaceless %}

  <div>

    <span>  hello  </span>

  </div>

{% endspaceless %}

Output: <div><span> hello </span></div>

widthratio

Calculate ratios for progress bars or scaling.

<!-- Calculate 25 out of 100 scaled to max-width 150 -->

{% widthratio score 100 150 %}

<!-- → 37 (floor of 25/100*150) -->



<!-- Progress bar width -->

<div class="bar" style="width: {% widthratio value max_value 100 %}px;"></div>

debug

Dump the current template context for debugging.

{% debug %}

Outputs a <pre> block with all context variables.


Security Tags

csrf_token

Output a hidden CSRF token input.

<form method="post">

  {% csrf_token %}

  <button type="submit">Submit</button>

</form>

The token value is HTML-escaped to prevent attribute injection. Requires csrf_token to be present in the template context.

app.use((req, res, next) => {

  res.locals.csrf_token = req.csrfToken();

  next();

});
app.use((req, res, next) => {

  res.locals.csrf_token = req.csrfToken();

  next();

});

csp_nonce_attr

Output a nonce attribute when csp_nonce is in the context.

<script {% csp_nonce_attr %} src="/js/app.js"></script>

If csp_nonce is present in context, the output is:

<script nonce="abc123" src="/js/app.js"></script>

If csp_nonce is not present, the tag outputs nothing.


Comments and Raw Output

comment / endcomment

Block comments ignored during parsing.

{% comment %}

  This is a comment.

  It can span multiple lines.

{% endcomment %}

verbatim / endverbatim

Treat content as raw text — template syntax is not parsed.

{% verbatim %}

  This will NOT be parsed: {{ user.name }}

  And this won't either: {% if x %}

{% endverbatim %}

You can also name a verbatim block:

{% verbatim myscript %}

  {{ angularExpression }}

{% endverbatim %}

Autoescape

Control HTML escaping for a block.

{% autoescape on %}

  {{ user_input }}  {# escaped → &lt;script&gt;... #}

{% endautoescape %}



{% autoescape off %}

  {{ trusted_html }}  {# not escaped → raw HTML #}

{% endautoescape %}

Library Loading

load

Activate a template library. Built-in libraries (humanize, cache, lorem) are auto-activated — you only need {% load %} for custom libraries you've registered.

{% load humanize %}

{{ views|intcomma }}

{{ count|ordinal }}
const { registerLibrary } = require('miki-template');



registerLibrary('myutils', {

  filters: {

    shout: (val) => String(val).toUpperCase() + '!'

  }

});
import { registerLibrary } from 'miki-template';



registerLibrary('myutils', {

  filters: {

    shout: (val) => String(val).toUpperCase() + '!'

  }

});

Then use in templates:

{% load myutils %}

{{ name|shout }}

Built-in libraries:

  • i18n — {% trans %}, {% blocktrans %}, {% language %}

  • humanize — intcomma, intword, apnumber, ordinal, naturalday

  • cache — {% cache timeout key %}...{% endcache %}

  • lorem — {% lorem %} tag and lorem filter


Template Tags

templatetag

Output literal template tag tokens. Useful when generating documentation or when the template syntax conflicts with another templating layer.

{% templatetag openblock %} if user.is_admin {% templatetag closeblock %}

<!-- Renders: {% if user.is_admin %} -->



{% templatetag openvariable %} name {% templatetag closevariable %}

<!-- Renders: {{ name }} -->

Available tokens:

| Token | Output |

|-------|--------|

| openblock | {% |

| closeblock | %} |

| openvariable | {{ |

| closevariable | }} |

| openbrace | { |

| closebrace | } |

| opencomment | {# |

| closecomment | #} |


Inheritance Tags

extends

Inherit from a parent template.

{% extends "base.html" %}

Can use expressions for dynamic parent selection:

{% extends device|default:"desktop/base.html" %}

Security: Path traversal is blocked — {% extends "../../etc/passwd" %} is rejected.

block / endblock

Define a block that can be overridden by child templates.

<!-- base.html -->

<html>

  <body>

    {% block content %}Default content{% endblock %}

  </body>

</html>
<!-- child.html -->

{% extends "base.html" %}

{% block content %}

  <h1>Child content</h1>

  {{ block.super }}

{% endblock %}

block.super

A special variable (not a tag). When used inside a {% block %}, it renders the parent template's version of that block.

See Template Inheritance for a detailed guide.

include

Include another template's content inline.

{% include "header.html" %}

{% include "header.html" with title="Hello" %}

{% include "header.html#partial_name" %}

{% include "header.html" with title="Hello" %}

Security: Path traversal is blocked.


Partial Tags

partialdef / endpartialdef

Define a reusable partial block.

{% partialdef card %}

  <div class="card">

    <h3>{{ title|default:"Untitled" }}</h3>

    <p>{{ body|truncatewords:30 }}</p>

    {% if featured %}<em>Featured</em>{% endif %}

  </div>

{% endpartialdef %}

Options:

| Option | Description |

|--------|-------------|

| inline | Renders the definition inline at its location during parse (the body appears in output AND registers for later use). |

{% partialdef greeting inline %}

  Hello {{ name }}!

{% endpartialdef %}

<!-- Above line ALSO outputs "Hello World!" when rendered -->

Programmatic access:

const { compile } = require('miki-template');

const compiled = compile(template);

compiled.renderPartial('card', { title: 'Hi', body: 'There' });
import { compile } from 'miki-template';

const compiled = compile(template);

compiled.renderPartial('card', { title: 'Hi', body: 'There' });

partial

Render a named partial.

{% partial card %}

{% partial card with title="Custom" body="World" %}

{% partial greeting with name=user.name %}

See Partial Templates for a detailed guide.


i18n Tags

trans

Translate a string.

{% trans "Hello, world!" %}

{% trans "Hello, %s!" name=user.name %}

{% trans context "verb" "He runs" %}

blocktrans / endblocktrans

Translate a block of text with variable interpolation and pluralization.

{% blocktrans with name=user.name %}

  Hello, {{ name }}!

{% endblocktrans %}



{% blocktrans count items|length %}

  {{ count }} item

{% plural %}

  {{ count }} items

{% endblocktrans %}

language / endlanguage

Switch language temporarily for a block.

{% language "fr" %}

  {% trans "Hello" %} → renders in French

{% endlanguage %}

Filter and Utility Tags

filter / endfilter

Apply a filter to a block of content.

{% filter upper %}

  Hello {{ user.name }}!

{% endfilter %}
{% filter center:"20" %}

  centered text

{% endfilter %}

verbatim / endverbatim

Render the body as raw text, ignoring template syntax inside.

{% verbatim %}

  {{ this_will_not_be_processed }}

{% endverbatim %}

resetcycle

Reset the cycle counter back to the beginning.

{% cycle "a" "b" "c" as marker silent %}

{{ marker }}

{% resetcycle %}

{% cycle "a" "b" "c" as marker silent %}

{{ marker }}

endfirstof

Closes a {% firstof %} block explicitly. It is a no-op and emits nothing.

{% firstof var1 var2 "default" %}

{% endfirstof %}

translate

Alias for {% trans %}. Works identically.

{% translate "Hello" %}

blocktranslate / endblocktranslate

Alias for {% blocktrans %}. Works identically.

{% blocktranslate %}

  Hello {{ name }}

{% endblocktranslate %}

Next Steps