diff --git a/reviewboard/attachments/mimetypes.py b/reviewboard/attachments/mimetypes.py
index eec67ba22aaff10c094380b8ff56a1aec9ed204b..a5aaaf395e734505d9551d4a5c16103f5b6acaab 100644
--- a/reviewboard/attachments/mimetypes.py
+++ b/reviewboard/attachments/mimetypes.py
@@ -1,9 +1,12 @@
 """File attachment mimetype registration and scoring."""
 
+from __future__ import annotations
+
 import docutils.core
 import logging
 import os
 import subprocess
+from typing import Optional, TYPE_CHECKING
 
 import mimeparse
 from django.contrib.staticfiles.storage import staticfiles_storage
@@ -12,6 +15,7 @@ from django.utils.html import format_html, format_html_join
 from django.utils.encoding import force_str, smart_str
 from django.utils.safestring import mark_safe
 from djblets.cache.backend import cache_memoize
+from djblets.siteconfig.models import SiteConfiguration
 from djblets.util.filesystem import is_exe_in_path
 from djblets.util.templatetags.djblets_images import thumbnail
 from pygments import highlight
@@ -20,6 +24,9 @@ from pygments.lexers import (ClassNotFound, guess_lexer_for_filename,
 
 from reviewboard.reviews.markdown_utils import render_markdown
 
+if TYPE_CHECKING:
+    from reviewboard.attachments.models import FileAttachment
+
 
 logger = logging.getLogger(__name__)
 
@@ -406,6 +413,19 @@ class MimetypeHandler(object):
         """
         raise NotImplementedError
 
+    def delete_associated_files(self) -> None:
+        """Delete any extra files associated with this attachment.
+
+        This should be implemented by subclasses who create and store extra
+        files for file attachments, such as handlers that create and store
+        thumbnail files. This should not delete the main file of the file
+        attachment.
+
+        Version Added:
+            6.0
+        """
+        pass
+
     def _get_mimetype_file(self, name):
         return '%s/%s.png' % (self.MIMETYPES_DIR, name)
 
@@ -415,6 +435,27 @@ class ImageMimetype(MimetypeHandler):
 
     supported_mimetypes = ['image/*']
 
+    def __init__(
+        self,
+        attachment: FileAttachment,
+        mimetype: str,
+    ) -> None:
+        """Initialize the handler.
+
+        Args:
+            attachment (reviewboard.attachments.models.FileAttachment):
+                The file attachment being handled.
+
+            mimetype (str):
+                The mimetype for the file attachment.
+        """
+        super().__init__(attachment, mimetype)
+
+        self._thumbnails = {
+            '1x': thumbnail(attachment.file, (300, None)),
+            '2x': thumbnail(attachment.file, (600, None)),
+        }
+
     def get_thumbnail(self):
         """Return a thumbnail of the image.
 
@@ -422,15 +463,39 @@ class ImageMimetype(MimetypeHandler):
             django.utils.safestring.SafeText:
             The HTML for the thumbnail for the associated attachment.
         """
+        thumbnails = self._thumbnails
+
         return format_html(
             '<div class="file-thumbnail">'
             ' <img src="{src_1x}" srcset="{src_1x} 1x, {src_2x} 2x"'
             ' alt="{caption}" width="300" />'
             '</div>',
-            src_1x=thumbnail(self.attachment.file, (300, None)),
-            src_2x=thumbnail(self.attachment.file, (600, None)),
+            src_1x=thumbnails['1x'],
+            src_2x=thumbnails['2x'],
             caption=self.attachment.caption)
 
+    def delete_associated_files(self) -> None:
+        """Delete the thumbnail files for this attachment.
+
+        Version Added:
+            6.0
+        """
+        siteconfig = SiteConfiguration.objects.get_current()
+        site_media_url = siteconfig.get('site_media_url')
+        storage = self.attachment.file.storage
+
+        for t in self._thumbnails.values():
+            filename: Optional[str] = None
+
+            if t.startswith(site_media_url):
+                filename = t[len(site_media_url):]
+
+            if filename and storage.exists(filename):
+                storage.delete(filename)
+            else:
+                logger.warning('Unable to find and delete thumbnail file '
+                               'at %s', t)
+
 
 class TextMimetype(MimetypeHandler):
     """Handles text mimetypes.
diff --git a/reviewboard/attachments/tests.py b/reviewboard/attachments/tests.py
index 7a598ad5ef047a8f82aa0ec34999d5e54d6090e9..ed185ed7a69da4af4cb3575af5d201015d846e7d 100644
--- a/reviewboard/attachments/tests.py
+++ b/reviewboard/attachments/tests.py
@@ -1,3 +1,7 @@
+"""Unit tests for file attachments and related functionality."""
+
+from __future__ import annotations
+
 import json
 import mimeparse
 import os
@@ -1194,3 +1198,74 @@ class TextMimetypeTests(SpyAgency, TestCase):
         thumbnail = self.file_attachment.thumbnail
 
         self.assertIsInstance(thumbnail, SafeText)
+
+
+class ImageMimetypeTests(BaseFileAttachmentTestCase):
+    """Unit tests for reviewboard.attachments.mimetypes.ImageMimetype.
+
+    Version Added:
+        6.0
+    """
+
+    fixtures = ['test_scmtools', 'test_users']
+
+    def setUp(self) -> None:
+        """Set up the test case."""
+        image_file = self.make_uploaded_file()
+
+        review_request = self.create_review_request(publish=True)
+
+        form = UploadFileForm(review_request, files={
+            'path': image_file,
+        })
+        self.assertTrue(form.is_valid())
+
+        self.file_attachment = form.create()
+
+    def test_get_thumbnail(self) -> None:
+        """Testing ImageMimetype.get_thumbnail"""
+        file = self.file_attachment.file
+        storage = file.storage
+        file_url_base = os.path.splitext(storage.url(file.name))[0]
+        filename_base = os.path.splitext(file.name)[0]
+
+        self.assertHTMLEqual(
+            self.file_attachment.thumbnail,
+            f'<div class="file-thumbnail">'
+            f'<img src="{file_url_base}_300.png" '
+            f'srcset="{file_url_base}_300.png 1x, {file_url_base}_600.png 2x"'
+            f'alt="" width="300" />'
+            f'</div>')
+
+        self.assertTrue(storage.exists(f'{filename_base}_300.png'))
+        self.assertTrue(storage.exists(f'{filename_base}_600.png'))
+
+    def test_delete_associated_files(self) -> None:
+        """Testing ImageMimetype.delete_associated_files"""
+        file = self.file_attachment.file
+        storage = file.storage
+        filename_base = os.path.splitext(file.name)[0]
+        filename_300 = f'{filename_base}_300.png'
+        filename_600 = f'{filename_base}_600.png'
+
+        self.file_attachment.thumbnail
+
+        self.assertTrue(storage.exists(filename_300))
+        self.assertTrue(storage.exists(filename_600))
+
+        self.file_attachment.mimetype_handler.delete_associated_files()
+
+        self.assertFalse(storage.exists(filename_300))
+        self.assertFalse(storage.exists(filename_600))
+
+        with self.assertLogs() as logs:
+            self.file_attachment.mimetype_handler.delete_associated_files()
+
+            self.assertEqual(
+                logs.records[0].getMessage(),
+                'Unable to find and delete thumbnail file at %s'
+                % storage.url(filename_300))
+            self.assertEqual(
+                logs.records[1].getMessage(),
+                'Unable to find and delete thumbnail file at %s'
+                % storage.url(filename_600))
