NetStacksNetStacks

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/render

You 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.

Local and offline

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:

  1. 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.
  2. Build the context — The variables object 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.
  3. Render with Jinja2 — The embedded engine parses the template, substitutes variables, evaluates conditionals, and expands loops to produce the final text.
  4. Return the result — The response is a RenderTemplateResponse with three fields: output (the rendered text), success (a boolean), and error (a message string, or null on 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 null becomes 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).

Preview before you deploy

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/render

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

FieldTypeDescription
outputstringThe rendered configuration text. Empty string when rendering fails.
successbooleantrue if the template parsed and rendered, otherwise false.
errorstring or nullThe failure message when success is false; null on success.
The document must be a Jinja template

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).

One call does it all

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.j2jinja2
{# 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-variables.jsonjson
{
  "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

bgp-rendered.txttext
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-East

Render 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.

render-template.shbash
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

render-success.jsonjson
{
  "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:

render-error.jsonjson
{
  "output": "",
  "success": false,
  "error": "Render error: ..."
}
Drive rendering from scripts

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/render with a flat variables object, or use the render dialog in the Terminal UI. The rendered text is returned in the output field 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), and error (a message string when success is false, otherwise null).
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 error field.
Q: How do I pass lists and nested objects?
A: Put them directly in the variables object. 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 the default filter or an is defined check.

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 uses ID instead of id.
  • Are booleans real booleans? The string "true" is truthy in Jinja2, but it is not the same as the boolean true.

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 pages for the templating workflow: