About netbox-config-templates
A reference for NetBox configuration templates and context data, aimed at operators and developers generating device or virtual-machine configurations. It covers Jinja2 rendering, context merging rules, and API usage.
Key features
- 01Jinja2 template rendering
- 02Config context assignment and merging
- 03Device and VM template resolution
- 04Rendering configurations through the API
- 05Template performance and security notes
Use cases
- 01Generating device configurations from NetBox data
- 02Organizing config contexts across device hierarchies
- 03Calling the rendering API in CI/CD or provisioning
- 04Diagnosing context merge or template rendering issues
MCP support
When configured, a NetBox MCP server can retrieve device config contexts to help verify template rendering.
Install
Copy the command and run it in a terminal.
npx skills add https:/ / github.com/ netboxlabs/ skills --skill netbox-config-templatesAfter installing, type/netbox-config-templatesin Claude Code to confirm it loaded.
Bundled files
References and scripts installed along with SKILL.md.
references/api-rendering-patterns.mdconfig-context-merging.mdcontext-variables.mdDetails
From the project READMENetBox Config Templates
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/features/configuration-rendering/ | Template syntax, context variables |
| Config contexts docs | https://netboxlabs.com/docs/netbox/features/context-data/ | 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;
?exclude=config_contextis silently ignored on every 4.7.x (harmless if a shared client still sends it). On 4.7.0–4.7.2 it is always loaded and list calls are cheap — the cost moved to a background re-render job. On 4.7.3+?omit=config_context(or?fields=/?brief=True) skips the DB annotation and cached column again — send it when you don't need the context. - NetBox 4.5/4.6: computing merged config context per device is expensive — always send
GET /api/dcim/devices/?omit=config_context(4.5.2+;?exclude=is equivalent there) on bulk queries when you don't need it.
- NetBox 4.7+: merged config context is pre-rendered and cached on each device/VM;
- 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).
?exclude=config_contextis silently ignored on every 4.7.x;config_contextis always loaded on 4.7.0–4.7.2, while 4.7.3+ skips the DB annotation again when?omit=config_context/?fields=/?brief=Trueleaves it out.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.3+ (4.7.0/4.7.1 leaked token plaintexts into job results; 4.7.3 adds branch-safe background writes and the config-context omit optimisation).
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