Your knowledge of NetBox config templates may be outdated. Template context variables, rendering API, and config context merge behavior evolve between releases. Prefer retrieval over pre-trained knowledge.
Retrieval Sources
| Source | URL / Method | Use for |
|---|---|---|
| Config templates docs | https://netboxlabs.com/docs/netbox/customization/config-templates/ |
Template syntax, context variables |
| Config contexts docs | https://netboxlabs.com/docs/netbox/customization/config-contexts/ |
Hierarchical data, merge rules |
| NetBox repo | https://github.com/netbox-community/netbox |
Template rendering source code |
| NetBox MCP server | If configured — fetch device config contexts to test template rendering | Verify context data |
NetBox provides a built-in Jinja2-based configuration rendering system. You define templates, populate data through config contexts, and render per-device or per-VM configurations via the API. This skill covers the entire workflow.
When to Use This Skill
- Generating device/VM configurations from NetBox data
- Setting up config contexts (hierarchical data) for template rendering
- Integrating NetBox config rendering into CI/CD or provisioning pipelines
- Troubleshooting template rendering issues or config context merge behavior
Quick Reference
Template Resolution Order (Device)
device.config_template → device.role.config_template → device.platform.config_template
First match wins. Only one template renders per device. VMs follow the same pattern (role → platform).
Key API Endpoints
| Action | Method | Endpoint |
|---|---|---|
| Render device config | POST | /api/dcim/devices/{id}/render-config/ |
| Render VM config | POST | /api/virtualization/virtual-machines/{id}/render-config/ |
| Render template directly | POST | /api/extras/config-templates/{id}/render/ |
| CRUD config templates | GET/POST/PUT/PATCH/DELETE | /api/extras/config-templates/ |
| CRUD config contexts | GET/POST/PUT/PATCH/DELETE | /api/extras/config-contexts/ |
Context Variables Cheat Sheet
In device/VM rendering (render-config):
| Variable | Type | Description |
|---|---|---|
device / virtualmachine |
ORM instance | Full model with all relations |
| (config context keys) | varies | Merged config context data as top-level keys |
| (POST body keys) | varies | Extra data sent with the render request |
In general-purpose rendering (config-templates/{id}/render/):
| Variable | Type | Description |
|---|---|---|
dcim, ipam, virtualization, etc. |
module-like | Access to all public NetBox model classes |
| (POST body keys) | varies | Extra data sent with the render request |
See references/context-variables.md for the complete breakdown.
Config Context System
Config contexts provide structured JSON data to templates. They are assigned based on object attributes and merged by weight.
Assignment Logic
A config context applies when the object matches all non-empty assignment categories (AND across categories) and any value within each category (OR within a category).
Assignment categories: Regions, Site groups, Sites, Locations, Device types, Roles, Platforms, Cluster types, Cluster groups, Clusters, Tenant groups, Tenants, Tags.
Merge Order
- All matching active config contexts, ordered by weight ascending, then name
- Higher-weight contexts overwrite lower-weight on key collision
- Local context data (on the device/VM itself) merges last and always wins
⚠️ Lists Are Replaced, Not Merged
deepmerge() merges dicts recursively but replaces non-dict values entirely:
// Weight 100: {"ntp_servers": ["10.0.0.1", "10.0.0.2"]}
// Weight 200: {"ntp_servers": ["10.1.1.1"]}
// Result: {"ntp_servers": ["10.1.1.1"]} ← NOT concatenated
Design data structures with this in mind. Use dicts with unique keys when you need additive merging.
Hierarchy Resolution
For hierarchical models (Region, Site Group, etc.), a context assigned to a parent also applies to all descendants. A context assigned to "Americas" region matches devices in "US-East" sub-region.
Pre-Rendered Cache (NetBox 4.7+)
On 4.7 the merged result is pre-rendered and cached on each device/VM. Any upstream change (a config context created/edited/deleted, or a device/VM attribute that affects which contexts apply) invalidates the cache and a non-blocking background job re-renders it; in the window between the two, reads fall back to on-demand rendering, so results are never stale. Merge semantics are unchanged. After upgrading, the upgrade script runs manage.py rebuild_config_context_cache (one UPDATE per device/VM; safe to interrupt and re-run). Bulk writes that bypass signals (queryset.update()) do not invalidate the cache — run rebuild_config_context_cache --force afterwards. On 4.5/4.6 config context is computed on every read.
For a deep dive on merging, see references/config-context-merging.md. For config context data modeling best practices, see netbox-data-modeling.
Writing Templates
Accessing Device Data
The device (or virtualmachine) variable is a full Django ORM instance:
hostname {{ device.name }}
!
{% if device.primary_ip4 %}
ip address {{ device.primary_ip4.address | ipaddr('address') }}
{% endif %}
!
{% for iface in device.interfaces.all() %}
interface {{ iface.name }}
description {{ iface.description | default('', true) }}
{% for ip in iface.ip_addresses.all() %}
ip address {{ ip.address }}
{% endfor %}
{% endfor %}
Using Config Context Data
Config context keys appear as top-level template variables:
{% for server in ntp_servers | default([]) %}
ntp server {{ server }}
{% endfor %}
!
{% for server in syslog_servers | default([]) %}
logging host {{ server }}
{% endfor %}
!
{% if snmp_community is defined %}
snmp-server community {{ snmp_community }} RO
{% endif %}
Template Inheritance (DataSource Required)
{% extends %} and {% include %} only work when templates are stored in a DataSource (git repo, S3, etc.). Templates defined with inline template_code cannot resolve other templates.
{# Stored in a DataSource alongside base.j2 #}
{% extends 'base.j2' %}
{% block interfaces %}
{% for iface in device.interfaces.all() %}
interface {{ iface.name }}
{% endfor %}
{% endblock %}
ORM Queries in Templates
Both device and general-purpose rendering expose NetBox models:
{# Query prefixes from IPAM #}
{% set mgmt_prefixes = ipam.Prefix.objects.filter(role__slug='management') %}
{% for pfx in mgmt_prefixes %}
ip route {{ pfx.prefix }} Management
{% endfor %}
Warning: ORM queries run unsandboxed against the database. A careless .all() on a large table will be slow. There is no query limiting.
Jinja2 Environment
- Templates run in a SandboxedEnvironment — no file I/O, no arbitrary imports
- Custom filters can be added in NetBox configuration via
JINJA_FILTERS(4.7+; the old nameJINJA2_FILTERSstill works but is deprecated and removed in 5.0) /JINJA2_FILTERS(4.5/4.6). Filter precedence on 4.7, lowest to highest: built-in (env) → plugin-registered → instanceJINJA_FILTERS. - Plugins can register Jinja filters and inject template context variables (NetBox 4.7+) via
jinja_env.py/register_jinja_filters()andPluginConfig.get_jinja_context()— see netbox-plugin-development for the how-to. Plugin context is global (not per-object) and must not reuse app-label names likedcim. - Custom Objects in templates (NetBox 4.7+, netbox-custom-objects v0.6.1+):
custom_objects.<type_name>.filter(device=device)or'<type_name>' | custom_objectsexposes custom object types by internal name; on 4.5/4.6custom_objectsis simply absent. See netbox-custom-objects. - Built-in
env()filter (NetBox 4.6.2+) — returns a system environment variable's value:{{ 'WEBHOOK_TOKEN_3' | env }}. Gated by theJINJA_ENVIRONMENT_PARAMSconfig list (an fnmatch wildcard allowlist of permitted variable names); returnsNonefor any name not matched. On 4.6.1 and earlier there are no built-in custom filters — only standard Jinja2 filters. - Per-template
environment_paramscan customize behavior (e.g.,undefined: jinja2.StrictUndefined), but on NetBox 4.6.1+ the keys are allowlisted — see below.
NetBox 4.6.1+ —
environment_paramsallowlist (CVE-2026-29514). A patched RCE viaenvironment_params(shared by ConfigTemplate and ExportTemplate) restricts which keys are accepted (JINJA_ENV_PARAMS_ALLOWED). Allowed: the standard delimiter/whitespace/scalar params (trim_blocks,lstrip_blocks,autoescape,keep_trailing_newline, block/comment/variable*_stringdelimiters, etc.) andundefined— butundefinedmust be one ofjinja2.StrictUndefined,jinja2.Undefined,jinja2.ChainableUndefined,jinja2.DebugUndefined.extensions,finalize,loader, andbytecode_cacheare blocked and a template setting them is rejected. The skill's examples (StrictUndefined,trim_blocks,lstrip_blocks) remain valid; do not recommend the blocked keys.
Common Vendor Patterns
Strategy 1: Platform-Based Template Assignment (Recommended)
Assign different config templates to different Platforms:
| Platform | Config Template |
|---|---|
junos |
junos-base.j2 |
ios-xe |
ios-xe-base.j2 |
eos |
eos-base.j2 |
Template resolves via: device → role → platform. This is the cleanest approach.
Strategy 2: Conditional Logic in a Single Template
{% if device.platform.slug == 'junos' %}
system {
host-name {{ device.name }};
}
{% elif device.platform.slug == 'ios-xe' %}
hostname {{ device.name }}
{% elif device.platform.slug == 'eos' %}
hostname {{ device.name }}
{% endif %}
Works for small differences but becomes unwieldy. Prefer Strategy 1 for production.
API Rendering Patterns
Render a Device Config
import requests
NETBOX = "https://netbox.example.com"
TOKEN = "nbt_abc123.xxxxxxxxxxxxxxxx"
headers = {"Authorization": f"Bearer {TOKEN}"}
# Render config for device ID 42
response = requests.post(
f"{NETBOX}/api/dcim/devices/42/render-config/",
headers=headers,
json={}, # optional extra context data
)
response.raise_for_status()
config = response.json()["content"]
Use Accept: text/plain header to get raw config output without JSON wrapping.
Override Config Context at Render Time
# POST body merges after config context (overrides on collision)
response = requests.post(
f"{NETBOX}/api/dcim/devices/42/render-config/",
headers=headers,
json={"ntp_servers": ["10.99.99.1"]}, # overrides config context
)
Render a Template Directly (No Device)
# General-purpose rendering — no device/VM in context
response = requests.post(
f"{NETBOX}/api/extras/config-templates/5/render/",
headers=headers,
json={"hostname": "switch01", "vlans": [10, 20, 30]},
)
For detailed API examples including pynetbox and error handling, see references/api-rendering-patterns.md.
Pitfalls
Performance
- Config context on list endpoints:
- NetBox 4.7+: merged config context is pre-rendered and cached on each device/VM and always included in the REST representation;
?exclude=config_contextis silently ignored (harmless if a shared client still sends it). List calls are cheap — the cost moved to a background re-render job. - NetBox 4.5/4.6: computing merged config context per device is expensive — always send
GET /api/dcim/devices/?exclude=config_contexton bulk queries when you don't need it.
- NetBox 4.7+: merged config context is pre-rendered and cached on each device/VM and always included in the REST representation;
- ORM queries in templates have no guardrails — avoid unbounded
.all()on large tables.
Template Behavior
- Template resolution is first-match only — device → role → platform. A device still has one assigned template. NetBox 4.6+ lets you render any other template against a device's context without changing assignments by passing
config_template_idin the render-config request body (or?config_template_id=<id>on the UI render URL) — useful for CI/dry-run rendering of alternative templates. (Requiresviewpermission on Config Template plusrender_configon the device.) - No template versioning — templates are mutable. Only NetBox's change log provides history.
- Render debugging (NetBox 4.6+) — when developing templates in a script/plugin, call
render_jinja2(..., debug=True)to enable Jinja2'sdebugextension ({% debug %}) for inspecting the available context. - extends/include requires DataSource — inline template_code cannot reference other templates.
Config Context Surprises
- Lists replace, don't append — the most common surprise. See merge rules above.
- POST data silently overrides config context — intentional but can be confusing when debugging unexpected values.
- environment_params vary per template — undefined variable handling may differ between templates if configured differently.
Version Notes
NetBox 4.7 (2026-09-02)
- Config context pre-rendered and cached per device/VM, invalidated on upstream changes and re-rendered by a background job (on-demand fallback in between).
config_contextis always in device/VM REST output;?exclude=config_contextis silently ignored.DeviceWithConfigContextSerializer/VirtualMachineWithConfigContextSerializermerged into the base serializers. Upgrade runsrebuild_config_context_cache. JINJA2_FILTERSrenamedJINJA_FILTERS(old name accepted with a deprecation warning until 5.0).- Plugins can register Jinja filters and inject context variables (
jinja_env.py,register_jinja_filters(),get_jinja_context()); precedence built-in → plugin → instanceJINJA_FILTERS. netbox-custom-objects v0.6.1+ uses this to exposecustom_objects.<type>/'<type>' | custom_objects. - New data reachable from templates:
device.cooling_method, interfacechannels/channel_id(channelized subinterfaces of typechannel), serviceport_mappings(list of"tcp/80"strings;protocol/portsdeprecated),device_type.end_of_life. Selection custom fields still read as raw values viadevice.cf/custom_field_datain templates — only the REST/GraphQL representation became{value, label}. - Platform: PostgreSQL 15+, Redis 6.0+,
ltreeextension; recommend 4.7.2+.
NetBox 4.6 (2026-05-05)
config_template_idoverride on render-config (4.6.0);render_jinja2(..., debug=True)(4.6.0);environment_paramsallowlist (4.6.1, CVE-2026-29514); built-inenvfilter gated byJINJA_ENVIRONMENT_PARAMS(4.6.2); autoescaping explicitly disabled for config templates rendered viaSandboxedEnvironment(4.6.5).
References
- references/context-variables.md — Complete context variable reference for all rendering modes
- references/config-context-merging.md — Deep dive on hierarchy, weight, and merge behavior
- references/api-rendering-patterns.md — Detailed API examples with pynetbox and requests