NetStacksNetStacks

Variables & Extraction

TeamsEnterprise

How 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:

TypeDescriptionExample
Shared variablesSame value for all devices in the stackdns_domain, ntp_servers, snmp_community
Per-device variablesDifferent value for each target devicehostname, 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:

  1. Per-device overrides — The highest priority. Override any variable for a specific device in the stack instance's device_overrides map
  2. Stack instance values — Values set in the variable_values map on the stack instance
  3. API variable sources — Values fetched automatically from an API endpoint using the ApiVariableConfig on the stack template. Each config specifies a resource_id, endpoint, method, and a json_path expression to extract the value from the response
  4. Template defaults — Fallback values defined in the template itself using the Jinja2 default filter
API Variable Auto-Population

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:

router-base.j2jinja2
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:

extracted-variables.jsonjson
[
  "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

multi-variable-patterns.j2jinja2
{# 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

render-request.jsonjson
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

rendered-output.txttext
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 Floor

API Variable Configuration

Configure automatic variable sourcing from a device API response:

api-variable-config.jsonjson
{
  "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 variables array 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_variables configuration 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 default filter 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's variable_values map.
Q: What happens if a required variable is missing at render time?
A: Jinja2 raises an UndefinedError and 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 add default filters 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 interface as the top-level variable. You then provide an object with an ip property 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-var or my.var will 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:

  1. Per-device overrides on the stack instance
  2. Stack instance variable_values
  3. API variable sources on the stack template
  4. 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.

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