diff --git a/InvenTree/report/migrations/0017_returnorderreport.py b/InvenTree/report/migrations/0017_returnorderreport.py
new file mode 100644
index 0000000000..e908051557
--- /dev/null
+++ b/InvenTree/report/migrations/0017_returnorderreport.py
@@ -0,0 +1,31 @@
+# Generated by Django 3.2.18 on 2023-03-15 11:17
+
+import django.core.validators
+from django.db import migrations, models
+import report.models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('report', '0016_auto_20210513_1303'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='ReturnOrderReport',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('name', models.CharField(help_text='Template name', max_length=100, verbose_name='Name')),
+                ('template', models.FileField(help_text='Report template file', upload_to=report.models.rename_template, validators=[django.core.validators.FileExtensionValidator(allowed_extensions=['html', 'htm'])], verbose_name='Template')),
+                ('description', models.CharField(help_text='Report template description', max_length=250, verbose_name='Description')),
+                ('revision', models.PositiveIntegerField(default=1, editable=False, help_text='Report revision number (auto-increments)', verbose_name='Revision')),
+                ('filename_pattern', models.CharField(default='report.pdf', help_text='Pattern for generating report filenames', max_length=100, verbose_name='Filename Pattern')),
+                ('enabled', models.BooleanField(default=True, help_text='Report template is enabled', verbose_name='Enabled')),
+                ('filters', models.CharField(blank=True, help_text='Return order query filters', max_length=250, validators=[report.models.validate_return_order_filters], verbose_name='Filters')),
+            ],
+            options={
+                'abstract': False,
+            },
+        ),
+    ]
diff --git a/InvenTree/report/models.py b/InvenTree/report/models.py
index 668fe0509c..ca9e526727 100644
--- a/InvenTree/report/models.py
+++ b/InvenTree/report/models.py
@@ -67,6 +67,11 @@ def validate_sales_order_filters(filters):
     return validateFilterString(filters, model=order.models.SalesOrder)
 
 
+def validate_return_order_filters(filters):
+    """Validate filter string against ReturnOrder model"""
+    return validateFilterString(filters, model=order.models.ReturnOrder)
+
+
 class WeasyprintReportMixin(WeasyTemplateResponseMixin):
     """Class for rendering a HTML template to a PDF."""
 
@@ -467,6 +472,42 @@ class SalesOrderReport(ReportTemplateBase):
         }
 
 
+class ReturnOrderReport(ReportTemplateBase):
+    """Render a custom report against a ReturnOrder object"""
+
+    @staticmethod
+    def get_api_url():
+        """Return the API URL associated with the ReturnOrderReport model"""
+        return reverse('api-return-order-report-list')
+
+    @classmethod
+    def getSubdir(cls):
+        """Return the directory where the ReturnOrderReport templates are stored"""
+        return 'returnorder'
+
+    filters = models.CharField(
+        blank=True,
+        max_length=250,
+        verbose_name=_('Filters'),
+        help_text=_('Return order query filters'),
+        validators=[
+            validate_return_order_filters,
+        ]
+    )
+
+    def get_context_data(self, request):
+        """Return custom context data for the ReturnOrderReport template"""
+
+        order = self.object_to_print
+
+        return {
+            'order': order,
+            'description': order.description,
+            'reference': order.reference,
+            'customer': order.customer,
+        }
+
+
 def rename_snippet(instance, filename):
     """Function to rename a report snippet once uploaded"""