Rendering & Preview
Preview rendered Jinja2 template output in NetStacks: render a template with a flat variables object and read back output, success, and inline error messages.
Overview
Rendering is the process of combining a Jinja2 template with a set of variable values to produce the final device configuration text. In NetStacks Terminal you preview a template before you ever touch a device: the agent renders the template locally and returns the result, so you can review the exact output and copy it wherever you need it.
Rendering is handled by a single endpoint provided by the local agent:
POST /api/docs/:id/renderYou pass a flat JSON variables object, and the response tells you whether rendering succeeded, the rendered output, and an inline error message when something goes wrong. There is no separate validate step and no device connection — syntax and rendering errors come back inline from the same call.
Template rendering runs entirely inside the local agent using the embedded Jinja2 engine (minijinja). It does not connect to any device, read a running config, or push commands. Rendering produces text and nothing more.
How It Works
The Rendering Pipeline
When you render a template, the agent follows this sequence:
- Load the document — The template is looked up by its
id. The document must be a Jinja template; rendering any other content type returns a validation error. - Build the context — The
variablesobject from the request body is passed straight through as the render context. It is a single flat JSON object — there is no shared/per-device merge layer and no override resolution at this stage. - Render with Jinja2 — The embedded engine parses the template, substitutes variables, evaluates conditionals, and expands loops to produce the final text.
- Return the result — The response is a
RenderTemplateResponsewith three fields:output(the rendered text),success(a boolean), anderror(a message string, ornullon success).
How variable values map into the template
The request variables object is converted into the Jinja2 context as you would expect:
- JSON strings, numbers, and booleans become Jinja2 scalars of the same type.
- JSON arrays become iterable lists you can drive with
{% for %}. - JSON objects become mappings you access with dotted attributes, e.g.
{{ neighbor.ip }}. - JSON
nullbecomes undefined — the same as omitting the key entirely.
Errors are returned inline
There is no separate syntax-validation endpoint. If the template fails to parse or fails to render, the same render call returns success: false, output as an empty string, and error set to a descriptive message. Common prefixes are Template parse error: for structural problems (unbalanced tags, bad syntax) and Render error: for problems that only surface while rendering (undefined variables, type mismatches).
Always render and review the output first. Because rendering is local and side-effect free, you can iterate on variable values as many times as you like before you copy the result into a session or hand it to a stack deployment.
Render API
A single endpoint renders a saved Jinja template. The :id path parameter is the document ID of the template.
POST /api/docs/:id/renderRequest body
The body has exactly one field, variables — a flat JSON object whose keys are the names your template references.
{
"variables": {
"bgp_asn": 65001,
"bgp_neighbors": [
{ "ip": "10.0.0.2", "remote_asn": 65002, "description": "Transit" }
]
}
}Response body
The response always has the same three fields:
| Field | Type | Description |
|---|---|---|
output | string | The rendered configuration text. Empty string when rendering fails. |
success | boolean | true if the template parsed and rendered, otherwise false. |
error | string or null | The failure message when success is false; null on success. |
If the target document is not a Jinja-type document, the request fails with a validation error rather than a render result — the render path only accepts Jinja templates.
Step-by-Step Guide
Step 1: Open the template
Open the Jinja template you want to preview. In the Terminal UI, the render dialog scans the template content and lists every variable it references, so you know exactly what values to supply.
Step 2: Provide variable values
Fill in a value for each variable. Variables detected as arrays let you add and remove rows; scalar variables take a single value. Through the API, you supply the same values as a flat variables object in the request body.
Step 3: Render and review the output
Render the template. On success, the rendered configuration text is returned in the output field — review it line by line to confirm it matches your intent, then copy it for use.
Step 4: Fix any inline errors
If success is false, read the error message. A Template parse error: means the template structure is broken (fix the template). A Render error: usually means a variable is missing or has the wrong type (fix the variable values or add a default filter).
There is no separate validation request. Rendering both parses and evaluates the template, so the single render call surfaces structural errors and runtime errors alike in the error field.
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-EastRender via the API (curl)
Send a flat variables object to the render endpoint for the template document ID. The response carries output, success, and error.
curl -X POST http://127.0.0.1:PORT/api/docs/DOC_ID/render \
-H "Content-Type: application/json" \
-d '{
"variables": {
"bgp_asn": 65001,
"bgp_neighbors": [
{ "ip": "10.0.0.2", "remote_asn": 65002, "description": "Transit" }
]
}
}'Successful Response
{
"output": "router bgp 65001\n neighbor 10.0.0.2 remote-as 65002\n neighbor 10.0.0.2 description Transit",
"success": true,
"error": null
}Failed Response (inline error)
When the template references something that was not provided, rendering fails and the message is returned in error with an empty output:
{
"output": "",
"success": false,
"error": "Render error: ..."
}Because the response shape is stable, you can branch on success in a CI job or a wrapper script: print output when true, and surface error when false. Replace PORT and DOC_ID with your local agent port and the template's document ID.
Questions & Answers
- Q: How do I preview a template before using it?
- A: Call
POST /api/docs/:id/renderwith a flatvariablesobject, or use the render dialog in the Terminal UI. The rendered text is returned in theoutputfield of the response. - Q: What does the render response look like?
- A: It always has three fields:
output(the rendered text, empty on failure),success(a boolean), anderror(a message string whensuccessisfalse, otherwisenull). - Q: Does rendering connect to a device or change anything?
- A: No. Rendering runs entirely in the local agent and only produces text. It does not open a session, read a running config, compute a diff, or push commands.
- Q: Is there a separate validate endpoint?
- A: No. There is no standalone validate call. The render endpoint parses and renders in one step, so syntax errors and runtime errors both come back inline in the
errorfield. - Q: How do I pass lists and nested objects?
- A: Put them directly in the
variablesobject. JSON arrays become iterable lists (use{% for %}) and JSON objects become mappings you access with dotted attributes (e.g.{{ neighbor.ip }}). - Q: What happens if a variable is missing?
- A: A missing key is treated as undefined. If the template requires it, rendering fails with a
Render error:message. Guard optional values with thedefaultfilter or anis definedcheck.
Troubleshooting
Response has success: false with a Render error
The template parsed but failed while rendering — most often because it references a variable that was not supplied, or a value has the wrong type. Compare the keys in your variables object against the names the template uses. Either add the missing value or guard it with the default filter or an is defined conditional.
Response has success: false with a Template parse error
The template structure is broken before any values are applied: unclosed {% if %}/{% for %} blocks, a misspelled tag, or invalid expression syntax. Fix the template source; no set of variable values will make a parse error go away.
Request fails saying the document is not a Jinja template
The render endpoint only accepts Jinja-type documents. Make sure the :id you are rendering points at a Jinja template rather than a plain note or other document type.
Output renders but looks wrong
Rendering succeeded, so the issue is in your values rather than the template structure:
- Are list variables actually arrays? A string where a list is expected will not loop correctly.
- Are object properties spelled correctly?
{{ vlan.id }}will not match an object that usesIDinstead ofid. - Are booleans real booleans? The string
"true"is truthy in Jinja2, but it is not the same as the booleantrue.
Output is empty even though success is true
An empty output with success: true usually means every line was inside conditional blocks that evaluated to false. Check that your boolean and is defined conditions match what you intend.
Unexpected whitespace in the output
Jinja2 tags can leave behind blank lines and trailing spaces. Use whitespace control — {%- -%} — to trim unwanted whitespace around block tags so the rendered config is clean.
Related Features
Related pages for the templating 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
- Example Templates — Ready-to-render templates for common configurations
- Templates API — Full API reference for working with templates