diff --git a/netbox/templates/base/base.html b/netbox/templates/base/base.html
index 443562027..2a433f845 100644
--- a/netbox/templates/base/base.html
+++ b/netbox/templates/base/base.html
@@ -4,6 +4,7 @@
{% load i18n %}
{% load django_htmx %}
{% load plugins %}
+{% load builtins %}
{% django_htmx_script %}
diff --git a/netbox/utilities/templatetags/builtins/tags.py b/netbox/utilities/templatetags/builtins/tags.py
index d1dd1a55a..f0cee4dea 100644
--- a/netbox/utilities/templatetags/builtins/tags.py
+++ b/netbox/utilities/templatetags/builtins/tags.py
@@ -1,5 +1,7 @@
from django import template
+from django.templatetags.static import static
from django.utils.safestring import mark_safe
+from urllib.parse import urlparse, urlunparse, parse_qs, urlencode
from extras.choices import CustomFieldTypeChoices
from utilities.querydict import dict_to_querydict
@@ -11,6 +13,7 @@ __all__ = (
'customfield_value',
'htmx_table',
'formaction',
+ 'static_with_params',
'tag',
)
@@ -127,3 +130,38 @@ def formaction(context):
if context.get('htmx_navigation', False):
return mark_safe('hx-push-url="true" hx-post')
return 'formaction'
+
+
+@register.simple_tag
+def static_with_params(path, **params):
+ """
+ Generate a static URL with properly appended query parameters.
+
+ This template tag handles the case where static files are served from AWS S3 or other
+ CDNs that already include query parameters in the URL. It properly appends additional
+ query parameters without creating double question marks.
+
+ Args:
+ path: The static file path (e.g., 'setmode.js')
+ **params: Query parameters to append (e.g., v='4.3.1')
+
+ Returns:
+ A properly formatted URL with query parameters
+ """
+ # Get the base static URL
+ static_url = static(path)
+
+ # Parse the URL to extract existing query parameters
+ parsed = urlparse(static_url)
+ existing_params = parse_qs(parsed.query)
+
+ # Add new parameters to existing ones
+ for key, value in params.items():
+ existing_params[key] = [str(value)]
+
+ # Rebuild the query string
+ new_query = urlencode(existing_params, doseq=True)
+
+ # Reconstruct the URL with the new query string
+ new_parsed = parsed._replace(query=new_query)
+ return urlunparse(new_parsed)