Variables & Extraction
How NetStacks discovers Jinja2 variables from a template to build the render form, and how you supply a flat JSON object of values at render time.
Overview
Variables are the bridge between a generic template and a device-specific configuration. When you write {{ hostname }} or {{ vlan_id }} in a Jinja2 template, you are declaring a placeholder that is replaced with a real value when the template is rendered.
NetStacks works with variables in two stages:
- Client-side extraction — When you open the render dialog for a Jinja template, the app parses the template text and discovers the variable names so it can build an input form for you. Extraction happens in the browser and is not persisted on the document.
- Value substitution at render time — You supply a flat JSON object of values. The agent renders the template with the minijinja engine and returns the finished text.
Template rendering and variable extraction are built-in features of NetStacks Terminal. No license, controller, or special tier is required.
How Variable Extraction Works
When you trigger a render, the template text is passed to a client-side extractor (extractJinjaVariables). The extractor scans the template line by line and returns one entry per discovered variable, each with a name, the line it was found on, any filters applied, and an isLoop flag.
It looks for variables in three places:
- Output expressions — anything inside {{ ... }}, for example {{ hostname }} or {{ hostname | upper }}.
- For-loop sources — the iterable in a {% for x in items %} block (
itemshere). These are flagged as arrays. - If-condition identifiers — the variable names referenced in a {% if ... %} condition, for example
enable_ospfin {% if enable_ospf %}.
For each variable the dialog calls inferVariableType to guess a type (string, number, boolean, or array) so it can show the right kind of input. The inference uses the isLoop flag and the filters applied to the expression — it is a UI hint only and does not change how the value is rendered.
The discovered variable list is computed fresh each time you open the render dialog. It is not saved to the document, so editing a template instantly changes the variables you are prompted for — there is nothing to re-sync or re-index.
Extraction Rules & Nuances
Knowing exactly what the extractor does (and does not) detect avoids surprises when filling in the render form.
Only the root name is captured
For an expression like {{ vlan.id }} or {{ vlans[0].name }}, the extractor keeps only the root token before the first dot or bracket —vlan and vlans respectively. You supply the whole object (or array of objects), and Jinja2 reaches into its properties at render time.
Loop iteration variables are skipped
In {% for server in ntp_servers %}{{ server }}{% endfor %} the iteration variable server is intentionally excluded — it is produced by the loop, not supplied by you. The loop source, ntp_servers, is captured and flagged with isLoop: trueso the form renders it as an array (multi-value) input.
Jinja keywords are ignored
Inside if-conditions, language keywords and literals such as and, or, not, in, is, true, false, and none are filtered out so they never appear as variables to fill in.
Each name appears once
A variable used many times is reported a single time. The first occurrence wins for the recorded line number and filters.
Only {{ ... }} output tags, {% for %} sources, and {% if %}conditions are scanned. A value written with single braces { hostname } is not Jinja2 syntax and will be emitted literally — it will not be extracted or substituted.
Step-by-Step Guide
Walk through the full variable workflow from authoring a template to rendering it with values.
Step 1: Write a template with variables
Create a Jinja template and use {{ variable_name }} placeholders wherever device-specific values belong:
hostname {{ hostname }}
!
interface Loopback0
ip address {{ router_id }} 255.255.255.255
!
router ospf 1
router-id {{ router_id }}
network {{ mgmt_network }} {{ mgmt_wildcard }} area 0
!
{% for server in ntp_servers %}
ntp server {{ server }}
{% endfor %}Step 2: Open the render dialog and review extracted variables
When you render this template, the extractor discovers the variables below. Note that the loop iteration variable server is omitted, while its source ntp_servers is treated as an array:
hostname (string)
router_id (string)
mgmt_network (string)
mgmt_wildcard (string)
ntp_servers (array) # loop source -> multi-value inputStep 3: Enter values
Fill in a value for each scalar variable and add one or more entries for the array variable. The dialog collects everything into a single flat JSON object.
Step 4: Render
The agent renders the template with minijinja and returns the finished configuration text, which you can copy or paste into a device session.
Code Examples
Template with several variable patterns
{# Patterns: simple, filtered, default, loop, nested object #}
hostname {{ hostname | upper }}
ip domain-name {{ domain | default('lab.local') }}
!
{% for nameserver in dns_servers %}
ip name-server {{ nameserver }}
{% endfor %}
!
{% for vlan in vlans %}
vlan {{ vlan.id }}
name {{ vlan.name }}
{% endfor %}
!
{% if enable_snmp %}
snmp-server community {{ snmp_community }} RO
snmp-server location {{ site_info.building }}, {{ site_info.floor }}
{% endif %}Variables the extractor reports (root names only, iteration variables excluded): hostname, domain, dns_servers (array), vlans (array), enable_snmp, snmp_community, site_info.
Render request
The render endpoint takes the document id in the path and a single flat variables object in the body:
POST /api/docs/{id}/render
Content-Type: application/json
{
"variables": {
"hostname": "DIST-SW-01",
"domain": "corp.example.com",
"dns_servers": ["10.0.0.53", "10.0.0.54"],
"vlans": [
{ "id": 100, "name": "USERS" },
{ "id": 200, "name": "VOICE" },
{ "id": 300, "name": "PRINTERS" }
],
"enable_snmp": true,
"snmp_community": "public-ro",
"site_info": { "building": "HQ-East", "floor": "3rd Floor" }
}
}Render response
The agent returns the rendered text along with a success flag and an optional error string:
{
"output": "hostname DIST-SW-01\nip domain-name corp.example.com\n!\nip name-server 10.0.0.53\nip name-server 10.0.0.54\n!\nvlan 100\n name USERS\nvlan 200\n name VOICE\nvlan 300\n name PRINTERS\n!\nsnmp-server community public-ro RO\nsnmp-server location HQ-East, 3rd Floor",
"success": true,
"error": null
}Rendered output
The output field, formatted:
hostname DIST-SW-01
ip domain-name corp.example.com
!
ip name-server 10.0.0.53
ip name-server 10.0.0.54
!
vlan 100
name USERS
vlan 200
name VOICE
vlan 300
name PRINTERS
!
snmp-server community public-ro RO
snmp-server location HQ-East, 3rd FloorError response
When rendering fails (for example a missing required variable), the agent still returns HTTP 200 with success: false and a message in error:
{
"output": "",
"success": false,
"error": "Render error: undefined value (in template:1)"
}Questions & Answers
- Q: How are variables extracted from templates?
- A: The render dialog parses the template text in the browser (
extractJinjaVariables) and collects every variable referenced in {{ }} output tags, {% for %} loop sources, and {% if %} conditions. This builds the input form. The list is computed on the fly and is not stored on the document. - Q: How do I supply values when rendering?
- A: Send a single flat JSON object under a
variableskey toPOST /api/docs/{id}/render. Keys are the variable names; values can be strings, numbers, booleans, arrays, or nested objects. The agent renders with minijinja and returns{ output, success, error }. - Q: Are loop variables prompted for?
- A: No. In {% for server in ntp_servers %} the iteration variable
serveris excluded because the loop produces it. The loop sourcentp_serversis prompted as an array, and you provide the list of items. - Q: How do nested object variables like {{ interface.ip }} work?
- A: The extractor captures only the root token
interface. You then supply an object with anipproperty, and Jinja2 resolves the dot path at render time. The same applies to bracket access such as {{ vlans[0].name }} — the root isvlans. - Q: How do I set default values?
- A: Use the Jinja2
defaultfilter directly in the template, for example {{ mtu | default(1500) }}. This supplies a fallback when the variable is omitted from thevariablesobject. - Q: What happens if a required variable is missing?
- A: minijinja treats it as undefined. If the template uses that value in a way that requires it, rendering fails and the response comes back with
success: falseand aRender errormessage identifying the problem. Add adefaultfilter for any variable that may be optional. - Q: Do I need a license or the controller to render templates?
- A: No. Variable extraction and rendering are free features of NetStacks Terminal and work entirely on the local agent.
Troubleshooting
A variable is not appearing in the render form
The extractor only inspects Jinja2 tags. Common causes:
- Using single braces
{ hostname }instead of double braces{{ hostname }}— single braces are not Jinja2 syntax. - The name is a loop iteration variable (the
xin {% for x in items %}). These are skipped by design; supply the loop source instead. - The token is a Jinja2 keyword or literal (
true,and,in, and so on) inside an if-condition — keywords are filtered out.
Nested object variables are not listed individually
This is expected. For {{ vlan.id }} the form prompts for vlan, not vlan.id. Provide a full object (or array of objects) whose properties match every path your template references.
Rendering fails with an undefined-value error
The response will have success: false and an error such as Render error: undefined value. Make sure every variable the template uses is present in the variables object, or add a default filter in the template so optional values fall back gracefully.
An array variable renders incorrectly
A loop source must be a JSON array. If you pass a single scalar where the template loops with {% for x in items %}, the loop body may not run as expected. In the render form, array-typed inputs accept multiple entries — add one row per item.
Related Features
Learn more about working with templates:
- Template Basics — Introduction to templates and the authoring workflow
- Rendering & Preview — Test templates with variable values before applying them
- Template Examples — Ready-to-use Jinja2 patterns for common configs
- Template Versioning — Track and roll back changes to template content
- Templates API — Programmatic access, including the render endpoint