NetStacksNetStacks

Template Basics

What configuration templates are in NetStacks -- Jinja documents you author, render with variables, and snapshot. Create your first template here.

Overview

A configuration template in NetStacks is a Jinja document: a piece of device configuration written once, with {{ variable }} placeholders standing in for the parts that change between devices. Instead of retyping the same interface descriptions, SNMP communities, and NTP servers across dozens of switches, you author the structure once and fill in device-specific values at render time.

Under the hood a template is just a Document stored in the Terminal's local SQLite database with category = "templates" and content_type = "jinja". It lives alongside your other documents (Outputs, Notes, Backups, MOPs) in the same Docs panel. There is no separate "template object" type and no server-side variable schema — the template is its raw Jinja source plus a name.

Templates solve several problems network engineers face daily:

  • Consistency — every device renders from the same baseline, eliminating drift caused by hand edits
  • Speed — produce a full config from a handful of variable values instead of typing it out
  • History — every save snapshots the previous content with a timestamp, so you can compare or restore earlier revisions
  • Safety — render and review the output before you paste anything into a live device
Free and open source

Template authoring, rendering, and version history are core features of the standalone NetStacks Terminal, which is free and open source (Apache-2.0). No license, account, or Controller is required. The endpoints described here are served by the local agent.

How It Works

The template lifecycle runs entirely inside the Terminal, from authoring to rendered output:

  1. Author — write configuration using Jinja syntax with {{ variable }} placeholders for the values that vary per device
  2. Save — the document is persisted with content_type = jinja. Each save also snapshots the previous content into the version history (see below)
  3. Collect variables — when you open the render dialog, the Terminal scans the template text in the browser and lists every variable it finds so it can prompt you for values
  4. Render — the agent combines the template with your variable values and returns the final configuration text. Review it before using it

The rendering engine is minijinja, a Rust implementation of the Jinja2 template language. It supports the familiar Jinja constructs — variable substitution, {% if %} conditionals, {% for %} loops, filters, and comments. Because it is a Rust template engine and not a Python interpreter, it has no concept of file access, network calls, or arbitrary code execution — a template can only transform the variables you pass into text.

Versioning is snapshot-based

There is no integer "version 1, version 2" counter. Each time you save a template, the prior content is stored as a timestamped snapshot in the version history. You can list those snapshots, view any one in full, and restore the document to an earlier snapshot. See Template Versioning.

Variable discovery is a UI convenience

The list of variables you see when rendering is computed in the browser by scanning {{ }}, {% for %}, and {% if %} expressions. It is not stored on the document and is not required — you can also call the render endpoint directly with any JSON object of variables.

Create Your First Template

Follow these steps to author and render your first template in the Terminal.

Step 1: Open the Docs panel

Open the Docs panel and find the Templates category. This category groups every document whose content type is Jinja.

Step 2: Create a new document in Templates

Add a new document under Templates. Give it a descriptive name and make sure the content type is Jinja Template. A naming convention that captures purpose and platform keeps things findable, for example snmp-config-ios or bgp-neighbor-nxos.

Naming convention

Use a consistent pattern like <purpose>-<platform>: snmp-config-ios, vlan-setup-nxos, ospf-area-junos.

Step 3: Write the template content

Use Jinja syntax. Place {{ }} placeholders where device-specific values belong:

snmp-config-ios.j2jinja2
hostname {{ hostname }}
!
snmp-server community {{ snmp_community }} RO
snmp-server location {{ location }}
snmp-server contact {{ contact }}

Step 4: Save

Save the document. Every name and the body (content) are required; there is no separate version number to manage. From the second save onward, the previous content is captured as a snapshot you can return to later.

Step 5: Render to preview the output

Open the Render Template dialog. The Terminal lists the variables it found (hostname, snmp_community, location, contact), you supply values, and it shows the final configuration. The dialog calls the render endpoint described in Rendering & Preview.

Code Examples

SNMP configuration template

An SNMP template for Cisco IOS with community strings, location, contact, and trap hosts. The default filter guards optional values, and a loop emits one host line per entry:

snmp-config-ios.j2jinja2
{# SNMP Configuration - Cisco IOS #}
{# Variables: snmp_community, snmp_rw_community, location, contact, trap_hosts #}

snmp-server community {{ snmp_community }} RO
{% if snmp_rw_community is defined and snmp_rw_community %}
snmp-server community {{ snmp_rw_community }} RW
{% endif %}
snmp-server location {{ location | default('DC1 Row-A Rack-12') }}
snmp-server contact {{ contact | default('[email protected]') }}
snmp-server enable traps
{% for host in trap_hosts %}
snmp-server host {{ host }} version 2c {{ snmp_community }}
{% endfor %}

Layer 3 interface template

An interface template with IP addressing, description, and optional HSRP:

interface-l3-ios.j2jinja2
{# Layer 3 Interface Configuration - Cisco IOS #}
{# Variables: interface_name, ip_address, subnet_mask, description, hsrp_enabled, hsrp_vip, hsrp_priority #}

interface {{ interface_name }}
 description {{ description }}
 ip address {{ ip_address }} {{ subnet_mask }}
 no ip proxy-arp
{% if hsrp_enabled | default(false) %}
 standby 1 ip {{ hsrp_vip }}
 standby 1 priority {{ hsrp_priority | default(100) }}
 standby 1 preempt
{% endif %}
 no shutdown

Rendering a template via the API

The Terminal's render dialog and any external script use the same endpoint: POST /api/docs/:id/render. The body is a JSON object with a variables key; the response contains output, success, and an optional error:

curl -s -X POST http://127.0.0.1:8080/api/docs/<DOCUMENT_ID>/render \
  -H "Content-Type: application/json" \
  -d '{
    "variables": {
      "snmp_community": "public-ro",
      "location": "DC1 Row-A Rack-12",
      "contact": "[email protected]",
      "trap_hosts": ["10.0.0.10", "10.0.0.11"]
    }
  }'

A successful response:

{
  "output": "snmp-server community public-ro RO\nsnmp-server location DC1 Row-A Rack-12\nsnmp-server contact [email protected]\nsnmp-server enable traps\nsnmp-server host 10.0.0.10 version 2c public-ro\nsnmp-server host 10.0.0.11 version 2c public-ro\n",
  "success": true,
  "error": null
}
Render only accepts Jinja documents

The endpoint checks that the target document's content type is jinja. Calling it against a non-template document returns a VALIDATION error such as Document is not a Jinja template. The exact host and port depend on how your agent is bound; adjust the URL to match your environment.

Questions & Answers

Q: What are configuration templates in NetStacks?
A: They are documents stored with category templates and content type jinja. You write device configuration once with {{ placeholder }} variables, then render the template with concrete values to produce final configuration text. They live in the Docs panel alongside your other documents.
Q: What templating language and engine does NetStacks use?
A: The Jinja2 template language, rendered by minijinja, a Rust Jinja2-compatible engine. It supports variable substitution ({{ }}), conditionals ({% if %}), loops ({% for %}), filters, and comments. Most templates written for Ansible or other Jinja2 tools work with only minor adjustments.
Q: How are variables defined in a template?
A: You do not declare them separately. Use {{ variable_name }} anywhere in the body. When you open the render dialog, the Terminal scans the text and lists the variables it finds so it can prompt you. Nothing is persisted as a variable schema — at render time you pass whatever JSON object you like.
Q: Can one template handle different device platforms?
A: Yes. Use a conditional to branch on a platform variable, for example {% if platform == 'cisco_ios' %} ... {% elif platform == 'juniper_junos' %} ... {% endif %}. Alternatively keep one template per platform and pick the right one when you render.
Q: Are templates versioned?
A: Yes, as a timestamped snapshot history rather than a numbered version counter. Each save records the previous content; you can list snapshots, view any one, and restore the document to an earlier snapshot. See Template Versioning.
Q: What happens if a referenced variable has no value at render time?
A: By default an undefined variable renders nothing, and using it in an unexpected way (such as iterating an undefined value) produces a render error. Guard optional values with the default filter, for example {{ contact | default('[email protected]') }}, and wrap optional blocks in {% if variable is defined %}.
Q: Can I deploy a rendered template to many devices at once?
A: Template authoring and rendering on this page are about producing configuration text. Pushing that configuration out to a fleet of devices is the job of Stacks, a separate deployment workflow. See Stacks.

Troubleshooting

Syntax errors when rendering

There is no separate "validate" step — syntax problems surface when you render. The render call returns success: false with a message in error. A parse problem (such as a missing {% endif %}) is prefixed Template parse error:, and a runtime problem is prefixed Render error::

{
  "output": "",
  "success": false,
  "error": "Template parse error: unexpected end of input, expected end of block (in template:2)"
}

Check that every {% if %} has a matching {% endif %} and every {% for %} a matching {% endfor %}.

A variable is not offered for input

The render dialog only prompts for variables it can find by scanning the text. Confirm you used double braces — {{ variable_name }}, not single braces like { variable_name }. You can always supply any variable directly in the render request body even if the dialog did not list it.

The document will not save

A document needs a name and body content. If saving is blocked, make sure the name is filled in and the editor is not empty.

Rendered output has stray blank lines

Block tags like {% for %} leave the line they sit on in the output. Use Jinja whitespace control — {%- ... -%} trims surrounding whitespace. See Jinja2 Syntax for the full rules.

Continue with these related pages:

  • Jinja2 Syntax — filters, conditionals, loops, and whitespace control supported by minijinja
  • Variables & Extraction — how the Terminal discovers variables and how you supply values
  • Template Versioning — snapshot history, viewing a snapshot, and restoring
  • Rendering & Preview — the render endpoint, request body, and response shape in detail
  • Example Templates — ready-to-use templates for VLAN, ACL, BGP, OSPF, and interface configs
  • Stacks — deploy rendered configuration out to a fleet of devices