Jinja2 templates for network configuration
In short: Jinja2 is a templating engine that merges data with a text template. Network teams use it to generate consistent configurations from YAML or a source of truth. Keep data and templates separate, use StrictUndefined to catch missing values, and review the rendered output before deploying.
Why templates?
Most configuration is the same pattern with different values: hostnames, VLANs, addresses. Copying and editing by hand is where typos and inconsistencies creep in. Jinja2 is a Python templating engine that lets you write the pattern once and fill in values from data. Ansible, Nornir and Salt all use it, so the skill transfers across tools.
Template syntax
Double braces insert values. Brace-percent tags hold logic such as loops and if statements. Filters, written after a pipe, transform values, for example | upper, | default("none") or | join(","). Here is an access switch template saved as templates/access.j2:
hostname {{ hostname }}
!
{% for vlan in vlans %}
vlan {{ vlan.id }}
name {{ vlan.name | upper }}
{% endfor %}
!
{% for port in ports %}
interface {{ port.name }}
description {{ port.description | default("user port") }}
switchport mode access
switchport access vlan {{ port.vlan }}
{% if port.voice_vlan is defined %}
switchport voice vlan {{ port.voice_vlan }}
{% endif %}
spanning-tree portfast
{% endfor %}
Rendering from Python
Install Jinja2 with pip install jinja2. Load templates from a folder, load the data from YAML, and render. Two settings matter a lot: trim_blocks and lstrip_blocks remove the blank lines that logic tags would otherwise leave, and StrictUndefined raises an error for missing variables instead of silently printing nothing.
import yaml
from jinja2 import Environment, FileSystemLoader, StrictUndefined
env = Environment(
loader=FileSystemLoader("templates"),
trim_blocks=True,
lstrip_blocks=True,
undefined=StrictUndefined,
)
template = env.get_template("access.j2")
with open("acc-sw1.yaml", encoding="utf-8") as f:
data = yaml.safe_load(f)
config = template.render(**data)
with open("output/acc-sw1.cfg", "w", encoding="utf-8") as f:
f.write(config)
print(config)
Matching data file acc-sw1.yaml:
hostname: acc-sw1
vlans:
- {id: 10, name: users}
- {id: 20, name: voice}
ports:
- {name: GigabitEthernet1/0/1, vlan: 10, voice_vlan: 20}
- {name: GigabitEthernet1/0/2, vlan: 10, description: printer}
Tip: render to files and review them, or diff them against the running configuration, before pushing anything. A template bug multiplies across every device.
Good practice and pitfalls
- Keep logic light: templates should mostly insert data. Complex decisions belong in Python or in the data itself.
- Validate data first: check VLAN ranges and addresses (for example with the ipaddress module) before rendering.
- No secrets in data files: inject passwords and keys at render time from a vault, and never commit rendered configs containing them.
- Autoescape is for HTML: network templates normally leave it off, but if you ever render web pages, turn autoescape on to prevent cross-site scripting.
- Never render untrusted templates: letting users supply template text can lead to server-side template injection. Users should supply data, not templates.
| Setting | Effect |
|---|---|
trim_blocks=True | Removes the newline after a block tag |
lstrip_blocks=True | Strips spaces before a block tag |
undefined=StrictUndefined | Missing variables raise an error |
Practice
Why use StrictUndefined?
Where should complex decisions live: the template or Python?
What is server-side template injection and how do you avoid it?
Key takeaways
- Jinja2 merges data with templates to produce consistent configs.
- Use {{ }} for values, {% %} for logic and | for filters.
- Enable trim_blocks, lstrip_blocks and StrictUndefined.
- Validate data, keep secrets out of files and review rendered output.
- Never render templates supplied by untrusted users.
Original content written by Mohamed Fayas for FM TecHub. © 2026 FM TecHub. You're welcome to share a link to this page; please don't copy the text elsewhere without permission.