Template Syntax (MiniJinja)
EnterpriseThe Jinja-style syntax NetStacks config templates support: variables, conditionals, loops, filters, tests, and the differences from Python Jinja2.
Overview
Config templates on the NetStacks Controller are rendered by MiniJinja, a Rust implementation of the Jinja template language. If you have written Jinja2 for Ansible or Python, most of what you know carries over: {{ }} prints a value, {% %} runs a statement, and | applies a filter. A few things differ, and they are listed under Differences from Python Jinja2.
The template editor reminds you of this with the hint Use MiniJinja syntax: {{ variable_name }} for variables. NetStacks adds no networking filters (such as ipaddr). You get the MiniJinja built-ins listed on this page, plus one small compatibility helper.
Templates are rendered as plain text. No HTML or XML auto-escaping is applied, so characters like < and & in a variable value are written exactly as given. That matters for XML templates. If a value can contain those characters, escape it yourself (for example with | replace("&", "&")).
Printing Variables
Wrap a variable name in double braces. Use dot or bracket notation to reach into objects and lists:
hostname {{ hostname }}
ntp server {{ ntp_servers[0] }}
description {{ uplink.description }}
snmp-server location {{ site["location"] }}Concatenate strings with ~ and use inline if expressions for simple choices:
description {{ "Uplink to " ~ peer_name }}
switchport mode {{ "trunk" if is_trunk else "access" }}Conditionals
if, elif, and else work as in Jinja2. An empty string, 0, an empty list, and a missing variable all count as false.
{% if voice_vlan %}
switchport voice vlan {{ voice_vlan }}
{% endif %}
{% if mode == "trunk" %}
switchport mode trunk
{% elif mode == "access" %}
switchport mode access
{% else %}
shutdown
{% endif %}Loops
Loop over lists with for. Inside a loop, the loop object gives you loop.index (from 1), loop.index0 (from 0), loop.first, loop.last, loop.length, loop.previtem, and loop.nextitem. An else block runs when the list is empty.
{% for server in ntp_servers %}
ntp server {{ server }}{% if loop.first %} prefer{% endif %}
{% else %}
! no NTP servers supplied
{% endfor %}Iterate over a dictionary with the items filter:
{% for vlan_id, name in vlans | items %}
vlan {{ vlan_id }}
name {{ name }}
{% endfor %}You can filter inline: {% for i in interfaces if i.enabled %}.
{% break %} and {% continue %} are not enabled. Use an inline if on the for tag, or the select/selectattr filters, to skip items.
set, Macros, and Comments
Assign values with set. To change a value from inside a loop, use namespace(), because plain set inside a loop only lasts for that iteration.
{% set mgmt_vlan = 99 %}
{% set ns = namespace(count=0) %}
{% for port in ports %}{% set ns.count = ns.count + 1 %}{% endfor %}
! {{ ns.count }} ports configuredMacros let you reuse a block inside the same template. Comments use {# ... #} and never appear in the output.
{# Access port macro #}
{% macro access_port(name, vlan) -%}
interface {{ name }}
switchport mode access
switchport access vlan {{ vlan }}
{%- endmacro %}
{{ access_port("GigabitEthernet1/0/10", 20) }}Each config template renders on its own. include, import, and extends cannot reach other templates. To combine several templates, add them as services in a stack.
Whitespace Control
Block tags leave their line breaks in the output. Add a minus sign to strip whitespace on that side of a tag: {%- trims before, -%} trims after. This works on {{- -}} too.
{% for server in ntp_servers -%}
ntp server {{ server }}
{% endfor -%}Check the result in Rendered Output while you edit (see Preview & Rendering). The trailing newline at the very end of a template is dropped.
Built-in Filters
Apply a filter with a pipe, for example {{ name | upper }}. Filters can be chained and some take arguments. These are the filters available in config templates:
| Group | Filters |
|---|---|
| Text | lower, upper, title, capitalize, trim, replace, split, lines, indent, format, string |
| Defaults and types | default (alias d), int, float, bool, list, abs, round |
| Lists | join, first, last, length (alias count), sort, reverse, unique, min, max, sum, batch, slice, chain, zip |
| Selecting and mapping | select, reject, selectattr, rejectattr, map, groupby, attr |
| Dictionaries | items, dictsort |
| Other | pprint, escape (alias e), safe |
Some examples that come up often in network configs:
logging buffered {{ buffer_size | default(16384) }}
switchport trunk allowed vlan {{ allowed_vlans | join(",") }}
description {{ description | upper | trim }}
{% for server in syslog_servers | unique | sort %}
logging host {{ server }}
{% endfor %}
{% for port in ports | selectattr("enabled") %}
interface {{ port.name }}
{% endfor %}
{% for line in banner_text | lines %}
{{ line }}
{% endfor %}tojson and urlencode are not enabled in config templates, and there are no IP-address filters. Build JSON output by writing the JSON structure in the template and inserting values with {{ }}.
Tests and Operators
Tests check a value with is: {% if voice_vlan is defined %}. Available tests:
defined,undefined,none,true,false,booleannumber,integer(aliasint),float,string,sequence,mapping,iterableodd,even,divisiblebystartingwith,endingwith,lower,upper,in,sameas- Comparison tests
eq,ne,lt,le,gt,ge(useful withselect, for examplevlans | select("gt", 1))
Operators: ==, !=, <, >, <=, >=, and, or, not, in, not in, arithmetic + - * / // % **, and ~ for string concatenation.
{% if interface is startingwith("Gigabit") %}
speed 1000
{% endif %}
{% if "10.0.0.1" in ntp_servers %}! primary NTP present{% endif %}Global Functions
| Function | Use |
|---|---|
range(start, stop, step) | Generate numbers, e.g. {% for n in range(1, 25) %} for ports 1 to 24. |
dict(key=value, ...) | Build a dictionary inside the template. |
namespace(...) | A container whose attributes you can update inside loops. |
get(object, "key") | NetStacks helper: read a key from an object, or undefined if it is missing. |
debug() | Print the current template context, handy while building a template. |
Differences from Python Jinja2
- No Python string or list methods. Write
{{ name | upper }}, not{{ name.upper() }}, and{% if name is startingwith("Gi") %}, notname.startswith("Gi"). .get()is supported as a special case. A call such as{{ port.get('vlan') }}is rewritten to{{ port.vlan }}before rendering, so templates written for Python keep working. Only the one-argument form with a quoted key is supported; for a fallback value use{{ port.vlan | default(1) }}.- No Ansible filters.
ipaddr,to_json,regex_replace, and similar Ansible or Netaddr filters do not exist. - No break/continue, include, import, or extends (see above).
Missing Variables
A variable that has no value renders as an empty string, an empty list in a for loop, and false in an if. A template never fails just because a top-level variable is blank. It does fail if you read an attribute of a missing variable (for example {{ uplink.name }} when uplink was not supplied). Guard those with {% if uplink is defined %}.
For required values, give a visible fallback so mistakes show up in the preview: {{ snmp_community | default("MISSING-COMMUNITY") }}.
Related Pages
- Variables: how values reach a template and how they are typed
- Preview & Rendering: test a template before you deploy it
- Example Templates: complete IOS-XE templates