Variables & Extraction
TeamsEnterpriseHow NetStacks automatically discovers variables from templates and how variable values are populated from manual entry, device data, and API sources.
Overview
Variables are the bridge between a generic template and a device-specific configuration. When you write {{ hostname }} or {{ vlan_id }} in a template, you are declaring a variable that will be replaced with a real value at render time.
NetStacks handles variables through two key mechanisms:
- Automatic extraction — When you save a template, NetStacks parses all Jinja2 expressions and builds a list of variable names. You do not need to declare variables separately; they are discovered from the template source
- Flexible population — Variable values can come from manual entry, device data, API resources, or stack-level defaults. This lets you automate value sourcing for large deployments
When templates are used in Stacks, variables fall into two categories:
| Type | Description | Example |
|---|---|---|
| Shared variables | Same value for all devices in the stack | dns_domain, ntp_servers, snmp_community |
| Per-device variables | Different value for each target device | hostname, mgmt_ip, router_id |
How It Works
Automatic Variable Extraction
When you create or update a template (POST or PUT to the templates API), NetStacks parses the Jinja2 source code and extracts every variable reference from {{ }} expressions. The discovered names are stored in the variables: string[] array on the JinjaTemplate object.
The extraction process finds variables in:
- Simple expressions: {{ hostname }}
- Expressions with filters: {{ hostname | upper }}
- Object attribute access: {{ vlan.id }} (extracts
vlan) - Conditional expressions: {% if enable_ospf %} (extracts
enable_ospf) - Loop targets: {% for intf in interfaces %} (extracts
interfaces)
Variable Population Sources
When deploying through a Stack, variable values can come from several sources, applied in priority order:
- Per-device overrides — The highest priority. Override any variable for a specific device in the stack instance's
device_overridesmap - Stack instance values — Values set in the
variable_valuesmap on the stack instance - API variable sources — Values fetched automatically from an API endpoint using the
ApiVariableConfigon the stack template. Each config specifies aresource_id,endpoint,method, and ajson_pathexpression to extract the value from the response - Template defaults — Fallback values defined in the template itself using the Jinja2
defaultfilter
The api_variables feature on Stack Templates lets you configure automatic value sourcing. For example, you can pull the current management IP from a device API response using a JSON path expression, eliminating manual data entry for large fleets.
Step-by-Step Guide
Walk through the full variable workflow from template authoring to deployment.
Step 1: Write a Template with Variables
Create a 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: Save and Review Extracted Variables
After saving, NetStacks extracts the following variables from the template:
[
"hostname",
"router_id",
"mgmt_network",
"mgmt_wildcard",
"ntp_servers"
]Step 3: Classify Variables in the Stack Template
When you create a Stack Template that uses this template, assign each variable as shared or per-device:
- Shared:
mgmt_network,mgmt_wildcard,ntp_servers(same for all devices) - Per-device:
hostname,router_id(unique per device)
Step 4: Provide Values at Deployment
When creating a Stack Instance, enter values for shared variables and per-device variables. NetStacks merges them at render time for each target device.
Step 5: Configure API Variable Sources (Optional)
For large deployments, configure api_variables on the Stack Template to auto-populate values from API endpoints instead of entering them manually.
Code Examples
Template with Various Variable Patterns
{# Variables demonstrated: simple, with filters, objects, lists #}
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 %}
!
snmp-server community {{ snmp_community }} RO
snmp-server location {{ site_info.building }}, {{ site_info.floor }}Extracted variables: hostname, domain, dns_servers, vlans, snmp_community, site_info
Providing Variable Values for Rendering
POST /plugins/stacks/admin/templates/render
Content-Type: application/json
{
"template_id": "tmpl_8f3a2b1c",
"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" }
],
"snmp_community": "public-ro",
"site_info": {
"building": "HQ-East",
"floor": "3rd Floor"
}
}
}Rendered Output
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 FloorAPI Variable Configuration
Configure automatic variable sourcing from a device API response:
{
"api_variables": {
"mgmt_ip": {
"resource_id": "res_device_info",
"endpoint": "/api/devices/{device_id}",
"method": "GET",
"json_path": "$.management.ip_address",
"description": "Device management IP from inventory"
},
"site_code": {
"resource_id": "res_device_info",
"endpoint": "/api/devices/{device_id}",
"method": "GET",
"json_path": "$.location.site_code",
"description": "Site code from device metadata"
}
}
}Questions & Answers
- Q: How are variables extracted from templates?
- A: NetStacks parses the Jinja2 template source when you save it and identifies all variable references in {{ }} expressions, {% if %} conditionals, and {% for %} loops. The discovered names are stored in the
variablesarray on the JinjaTemplate object. You do not need to declare or register variables manually. - Q: What is the difference between shared and per-device variables?
- A: Shared variables have the same value for every device in a stack deployment — for example, NTP servers, DNS domain, or SNMP community strings. Per-device variables have a unique value for each target device — for example, hostname, management IP, or router ID. You classify variables as shared or per-device when configuring the Stack Template.
- Q: Can variables be auto-populated from device data?
- A: Yes. The Stack Template supports an
api_variablesconfiguration where you define an API endpoint and a JSON path expression for each variable. At deployment time, NetStacks fetches the endpoint for each device and extracts the value using the specified JSON path. This eliminates manual data entry for values that already exist in your device inventory or external systems. - Q: How do I set default values for variables?
- A: Use the Jinja2
defaultfilter in the template itself: {{ mtu | default(1500) }}. This provides a fallback value when the variable is not supplied at render time. For complex defaults, you can also set values in the stack instance'svariable_valuesmap. - Q: What happens if a required variable is missing at render time?
- A: Jinja2 raises an
UndefinedErrorand the rendering fails. The error message identifies which variable is undefined. To prevent this, either ensure all variables have values in the deployment context or adddefaultfilters to your template for optional variables. - Q: Can I use nested object variables like {{ interface.ip }} in templates?
- A: Yes. Jinja2 supports dot notation for object attribute access and bracket notation for dictionary keys. When you use {{ interface.ip }}, the extraction process identifies
interfaceas the top-level variable. You then provide an object with anipproperty in your variable values.
Troubleshooting
Variables not appearing in the extracted list
The extractor only detects variables inside Jinja2 expression tags. Common reasons a variable might not be extracted:
- Using single braces
{ hostname }instead of double braces{{ hostname }} - Variable is only used inside a Jinja2 comment:
{# {{ hostname }} #}— comments are stripped before extraction - Variable name contains invalid characters. Names must be valid Python identifiers: letters, numbers, and underscores only. Names like
my-varormy.varwill cause parse errors
Nested object variables not detected individually
When you use {{ vlan.id }}, the extractor identifies vlan as the variable, not vlan.id. This is correct — you provide the vlan object and Jinja2 accesses its id property at render time. Make sure the object you provide has all the properties your template references.
Missing variable values at render time
If rendering fails with an UndefinedError, check these sources in order:
- Per-device overrides on the stack instance
- Stack instance variable_values
- API variable sources on the stack template
- Default filter in the template source
If none of these provide a value, the render will fail. Add a default filter to the template for any variable that might not always have a value.
API variable source returns wrong value
Verify the json_path expression against the actual API response. Test the endpoint manually to confirm the response structure matches what the JSON path expects. Common issue: the API returns an array where the path expects an object.
Related Features
Learn more about working with variables in the template ecosystem:
- Template Basics — Introduction to templates and the authoring workflow
- Jinja2 Syntax — Filters, conditionals, loops, and macros for transforming variable output
- Rendering & Preview — Test your templates with variable values before deployment
- Variable Overrides — Per-device overrides for customizing variable values in stack deployments