Rendering & Preview
TeamsEnterpriseHow to preview rendered template output, validate Jinja2 syntax, diff against running configs, and use dry-run mode before deploying.
Overview
Rendering is the process of combining a Jinja2 template with variable values to produce the final device configuration text. NetStacks provides two endpoints that let you validate and preview templates before deploying to production devices:
- Validate (POST /plugins/stacks/admin/templates/validate) — Check Jinja2 syntax without requiring variable values. This catches unclosed tags, invalid filter names, and structural errors
- Render (POST /plugins/stacks/admin/templates/render) — Combine the template with a set of variable values and return the rendered configuration text
Beyond preview, the deployment system supports a dry_run mode that renders the template, generates the device commands, and computes a diff against the running config — all without pushing any changes to the device. This gives you complete visibility into what will change before you commit.
How It Works
The Rendering Pipeline
When you render a template, NetStacks follows this sequence:
- Load template source — The raw Jinja2 text is retrieved from the database using the
template_id - Merge variables — Shared variables, per-device variables, and any overrides are combined into a single context dictionary. Per-device overrides take highest priority
- Render in sandbox — The Jinja2 engine processes the template in a sandboxed environment, substituting variables, evaluating conditionals, and expanding loops
- Return output — The rendered string is returned in a
RenderTemplateResponsewith a singlerenderedfield containing the final configuration text
Validation (Syntax Check)
The validation endpoint parses the template source without rendering it. This means it does not need variable values — it only checks that the Jinja2 syntax is structurally correct. Use this to catch errors early, before you have variable values ready.
Dry-Run Deployment
The deploy action supports a dry_run: true flag. In dry-run mode, NetStacks:
- Renders the template with the provided variables
- Generates the device commands that would be pushed
- Computes a
diff_summarycomparing the rendered config against the device's current running config - Returns all of this without making any changes to the device
For production deployments, run with dry_run: true first to review the exact commands and diff. Then deploy with dry_run: false once you have confirmed the changes are correct.
Step-by-Step Guide
Step 1: Select a Template
Open the template you want to preview in the template editor. You can also render any template by its ID through the API.
Step 2: Provide Variable Values
Enter values for each variable. In the UI, the variable panel shows all extracted variables with input fields. Through the API, pass a variables object in the render request body.
Step 3: Preview the Rendered Output
Click Preview (or call the render endpoint). The rendered configuration text appears in the output panel. Review it line by line to confirm the output matches your expectations.
Step 4: Compare Against Running Config
When deploying through a stack, use dry-run mode to see a diff between the rendered template and the device's current running configuration. The diff highlights lines that will be added, removed, or changed.
Step 5: Validate Syntax
Before saving a template, use the validation endpoint to check for syntax errors. Validation runs independently of variable values, so you can check syntax even before you know what values you will use.
Validation checks syntax only (are tags balanced? are filter names valid?). Rendering goes further by substituting actual values and producing output. A template can pass validation but still fail rendering if required variables are missing.
Step 6: Deploy with Confidence
After confirming the preview and dry-run results, deploy with dry_run: false. The rendered configuration is pushed to the target device.
Code Examples
BGP Neighbor Template
{# BGP Neighbor Configuration - Cisco IOS #}
router bgp {{ bgp_asn }}
{% for neighbor in bgp_neighbors %}
neighbor {{ neighbor.ip }} remote-as {{ neighbor.remote_asn }}
neighbor {{ neighbor.ip }} description {{ neighbor.description | default('BGP Peer') }}
{% if neighbor.route_map_in is defined %}
neighbor {{ neighbor.ip }} route-map {{ neighbor.route_map_in }} in
{% endif %}
{% if neighbor.route_map_out is defined %}
neighbor {{ neighbor.ip }} route-map {{ neighbor.route_map_out }} out
{% endif %}
{% endfor %}Variable Values
{
"bgp_asn": "65001",
"bgp_neighbors": [
{
"ip": "10.0.0.2",
"remote_asn": "65002",
"description": "Transit - Provider-A",
"route_map_in": "RM-PROVIDER-A-IN",
"route_map_out": "RM-PROVIDER-A-OUT"
},
{
"ip": "10.0.0.6",
"remote_asn": "65003",
"description": "Peering - IX-East"
}
]
}Rendered Output
router bgp 65001
neighbor 10.0.0.2 remote-as 65002
neighbor 10.0.0.2 description Transit - Provider-A
neighbor 10.0.0.2 route-map RM-PROVIDER-A-IN in
neighbor 10.0.0.2 route-map RM-PROVIDER-A-OUT out
neighbor 10.0.0.6 remote-as 65003
neighbor 10.0.0.6 description Peering - IX-EastValidate Template Syntax
POST /plugins/stacks/admin/templates/validate
Content-Type: application/json
Authorization: Bearer <token>
{
"template_source": "router bgp {{ bgp_asn }}\n{% for neighbor in bgp_neighbors %}\n neighbor {{ neighbor.ip }} remote-as {{ neighbor.remote_asn }}\n{% endfor %}"
}
# Response (success):
{
"valid": true
}Render Template via API
POST /plugins/stacks/admin/templates/render
Content-Type: application/json
Authorization: Bearer <token>
{
"template_id": "tmpl_8f3a2b1c",
"variables": {
"bgp_asn": "65001",
"bgp_neighbors": [
{ "ip": "10.0.0.2", "remote_asn": "65002", "description": "Transit" }
]
}
}
# Response:
{
"rendered": "router bgp 65001\n neighbor 10.0.0.2 remote-as 65002\n neighbor 10.0.0.2 description Transit"
}Dry-Run Deployment
POST /plugins/stacks/admin/actions/deploy
Content-Type: application/json
Authorization: Bearer <token>
{
"stack_instance_id": "si_abc123",
"device_id": "dev_xyz789",
"credential_id": "cred_456",
"template_id": "tmpl_8f3a2b1c",
"variables": { "bgp_asn": "65001", "bgp_neighbors": [] },
"platform": "cisco_ios",
"dry_run": true
}
# Response:
{
"success": true,
"commands": [
"router bgp 65001"
],
"pushed": false,
"push_output": null,
"error": null,
"diff_summary": "+ router bgp 65001"
}Questions & Answers
- Q: How do I preview a template before deploying?
- A: Use the render endpoint (POST /plugins/stacks/admin/templates/render) with the template ID and a set of variable values. The response contains the
renderedfield with the complete configuration text. In the UI, fill in variable values and click Preview. - Q: Can I render a template without deploying to any device?
- A: Yes. The render endpoint only produces text output — it does not connect to any device. For deployment preview with a diff against the running config, use the deploy action with
dry_run: true. - Q: What is dry-run mode?
- A: Dry-run mode (
dry_run: trueon the deploy action) renders the template, generates the device commands, and computes a diff against the running configuration — all without pushing any changes to the device. The response includes the command list and adiff_summaryshowing what would change. - Q: How does the diff preview work?
- A: During a dry-run deployment, NetStacks connects to the target device, reads its current running configuration, renders the template, and computes the difference. The
diff_summaryin the response shows added lines (prefixed with +) and removed lines (prefixed with -). - Q: Can I validate template syntax without providing variable values?
- A: Yes. The validation endpoint (POST /plugins/stacks/admin/templates/validate) only checks Jinja2 syntax structure. It verifies that tags are balanced, filter names are valid, and the template parses correctly. It does not need variable values because it does not render the template.
- Q: What errors does validation catch?
- A: Validation catches structural Jinja2 errors: unclosed tags (missing endif, endfor, endmacro), unknown filter names, invalid syntax inside expressions, and malformed block structures. It does not catch runtime errors like undefined variables, which only appear during rendering.
Troubleshooting
UndefinedError during rendering
The template references a variable that was not provided in the render request. Check the variables object in your request against the template's extracted variable list. Either add the missing variable or use the default filter in the template.
Template validates successfully but renders incorrectly
Validation only checks syntax, not logic. If the rendered output is wrong, review your variable values:
- Are list variables actually arrays? A string where a list is expected will not loop correctly
- Are object properties spelled correctly?
{{ vlan.id }}will fail if the object hasIDinstead ofid - Are boolean values actual booleans? The string
"true"is truthy in Jinja2, but it is not the same as the booleantrue
Diff shows unexpected changes
If the dry-run diff shows more changes than expected, the issue is usually whitespace or ordering differences between the rendered template and the running config. Jinja2 templates may produce trailing spaces or extra blank lines that differ from the device's formatted output. Use whitespace control ({%- -%}) to trim unwanted whitespace.
Render endpoint returns empty output
An empty rendered string usually means all content was inside conditional blocks that evaluated to false. Check that your boolean variables have the correct values and that {% if %} conditions match what you intend.
Related Features
Related pages for the rendering and deployment workflow:
- Template Basics — Introduction to templates and how to create them
- Jinja2 Syntax — Filters, conditionals, loops, and whitespace control
- Variables & Extraction — How variables are discovered and populated for rendering
- Stack Instances — Deploy rendered templates to target devices
- Creating Stacks — Build deployment stacks with templates, devices, and variables