diff --git a/netbox/netbox/ui/attrs.py b/netbox/netbox/ui/attrs.py
index 999e02f94..37cc1ba12 100644
--- a/netbox/netbox/ui/attrs.py
+++ b/netbox/netbox/ui/attrs.py
@@ -24,11 +24,13 @@ __all__ = (
PLACEHOLDER_HTML = '—'
+IMAGE_DECODING_CHOICES = ('auto', 'async', 'sync')
#
# Attributes
#
+
class ObjectAttribute:
"""
Base class for representing an attribute of an object.
@@ -193,9 +195,37 @@ class ColorAttr(ObjectAttribute):
class ImageAttr(ObjectAttribute):
"""
An attribute representing an image field on the model. Displays the uploaded image.
+
+ Parameters:
+ load_lazy (bool): If True, the image will be loaded lazily (default: True)
+ decoding (str): Image decoding option ('async', 'sync', 'auto', None)
"""
template_name = 'ui/attrs/image.html'
+ def __init__(self, *args, load_lazy=True, decoding=None, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.load_lazy = load_lazy
+
+ if decoding is not None and decoding not in IMAGE_DECODING_CHOICES:
+ raise ValueError(
+ _('Invalid decoding option: {decoding}! Must be one of {image_decoding_choices}').format(
+ decoding=decoding, image_decoding_choices=', '.join(IMAGE_DECODING_CHOICES)
+ )
+ )
+
+ # Compute default decoding:
+ # - lazy images: async decoding (performance-friendly hint)
+ # - non-lazy images: omit decoding (browser default/auto)
+ if decoding is None and load_lazy:
+ decoding = 'async'
+ self.decoding = decoding
+
+ def get_context(self, obj, context):
+ return {
+ 'decoding': self.decoding,
+ 'load_lazy': self.load_lazy,
+ }
+
class RelatedObjectAttr(ObjectAttribute):
"""
diff --git a/netbox/templates/ui/attrs/image.html b/netbox/templates/ui/attrs/image.html
index 3c10113c4..895b1788f 100644
--- a/netbox/templates/ui/attrs/image.html
+++ b/netbox/templates/ui/attrs/image.html
@@ -1,3 +1,3 @@
-
+