Skip to content

Jinja2 Config Templates

NetBox uses Jinja2 to render configuration templates. Plugins can extend this rendering pipeline in two complementary ways:

  1. Register custom filters — make new template filters available by name in every config template.
  2. Inject context variables — add extra variables that are available inside every config template render.

Registering Jinja2 Filters

Via jinja2_env.py (auto-discovery)

Create a file named jinja2_env.py in your plugin root and expose a dict called filters. NetBox will auto-discover and register it when the plugin loads.

my_plugin/jinja2_env.py
def prefix_list(device):
    """Return all prefixes assigned to a device's interfaces."""
    return [
        str(ip.address)
        for iface in device.interfaces.all()
        for ip in iface.ip_addresses.all()
    ]

filters = {
    'prefix_list': prefix_list,
}

The filter is then available in any config template:

{% for prefix in device | prefix_list %}
  network {{ prefix }}
{% endfor %}

Via register_jinja2_filters()

You can also register filters programmatically inside your plugin's ready() method:

my_plugin/__init__.py
from netbox.plugins import PluginConfig

class MyPluginConfig(PluginConfig):
    name = 'my_plugin'
    # ...

    def ready(self):
        super().ready()
        from netbox.plugins.registration import register_jinja2_filters
        from .jinja2_env import filters
        register_jinja2_filters(filters)

register_jinja2_filters() accepts a dict mapping filter names to callables. It raises TypeError if passed a non-dict or if any value is not callable.

Precedence

The full filter precedence from lowest to highest is: NetBox built-in filters (e.g. env) → plugin-registered filtersinstance JINJA2_FILTERS. Instance-level filters always win, so site admins can override anything without touching a plugin.

If two plugins register a filter with the same name, the later-loaded plugin's version wins and NetBox will log a warning.

For example, if my_plugin registers a prefix_list filter but a site needs different behaviour, the operator can replace it in configuration.py without touching the plugin:

configuration.py
def prefix_list(device):
    # Site-local override: include only loopback prefixes
    return [
        str(ip.address)
        for iface in device.interfaces.filter(type='loopback')
        for ip in iface.ip_addresses.all()
    ]

JINJA2_FILTERS = {
    'prefix_list': prefix_list,
}

Injecting Context Variables

Override get_jinja2_context() in your PluginConfig subclass to inject additional variables into every config template render context.

my_plugin/__init__.py
from netbox.plugins import PluginConfig

class MyPluginConfig(PluginConfig):
    name = 'my_plugin'
    # ...

    def get_jinja2_context(self):
        from .utils import MyNamespace
        return {
            'my_plugin': MyNamespace(),
        }

The returned dict is merged into the template context, so my_plugin becomes available by name inside every config template:

{% set records = my_plugin.lookup(device.name) %}

Startup cost

get_jinja2_context() is called on every config template render, not once at startup. Keep it fast. Defer expensive lookups to the object you return rather than performing them in get_jinja2_context() itself.

Conflict avoidance

Choose context variable names that are unlikely to collide with NetBox's built-in template variables (device, queryset, etc.) or with those contributed by other plugins. Prefixing with your plugin name is strongly recommended.

In addition, avoid top-level app-label names (dcim, ipam, virtualization, etc.). The auto-populated template context maps each app label to a dict of its public model classes; returning a key like 'dcim' from get_jinja2_context() will silently replace that entire namespace.

No per-render context

get_jinja2_context() receives no arguments — it has no access to the object being rendered or the caller-supplied context. It is intended for plugin-global namespaces (e.g. a lazily-evaluated query helper). Per-object logic belongs in the template itself or in a custom filter.