diff --git a/InvenTree/InvenTree/helpers.py b/InvenTree/InvenTree/helpers.py
index d36f93704e..d43a462f92 100644
--- a/InvenTree/InvenTree/helpers.py
+++ b/InvenTree/InvenTree/helpers.py
@@ -913,3 +913,10 @@ def inheritors(cls: type[Inheritors_T]) -> set[type[Inheritors_T]]:
 def is_ajax(request):
     """Check if the current request is an AJAX request."""
     return request.headers.get('x-requested-with') == 'XMLHttpRequest'
+
+
+def pui_url(subpath: str) -> str:
+    """Return the URL for a PUI subpath."""
+    if not subpath.startswith('/'):
+        subpath = '/' + subpath
+    return f'/{settings.FRONTEND_URL_BASE}{subpath}'
diff --git a/InvenTree/InvenTree/settings.py b/InvenTree/InvenTree/settings.py
index d425182a79..0e995843f5 100644
--- a/InvenTree/InvenTree/settings.py
+++ b/InvenTree/InvenTree/settings.py
@@ -80,6 +80,9 @@ DEBUG = get_boolean_setting('INVENTREE_DEBUG', 'debug', True)
 ENABLE_CLASSIC_FRONTEND = get_boolean_setting(
     'INVENTREE_CLASSIC_FRONTEND', 'classic_frontend', True
 )
+# Disable CUI parts if CUI tests are disabled
+if TESTING and '--exclude-tag=cui' in sys.argv:
+    ENABLE_CLASSIC_FRONTEND = False
 ENABLE_PLATFORM_FRONTEND = get_boolean_setting(
     'INVENTREE_PLATFORM_FRONTEND', 'platform_frontend', True
 )
diff --git a/InvenTree/InvenTree/test_middleware.py b/InvenTree/InvenTree/test_middleware.py
index 8f091f34da..c4fcc52858 100644
--- a/InvenTree/InvenTree/test_middleware.py
+++ b/InvenTree/InvenTree/test_middleware.py
@@ -2,6 +2,7 @@
 
 from django.conf import settings
 from django.http import Http404
+from django.test import tag
 from django.urls import reverse
 
 from error_report.models import Error
@@ -10,6 +11,8 @@ from InvenTree.exceptions import log_error
 from InvenTree.unit_test import InvenTreeTestCase
 
 
+# TODO change test to not rely on CUI
+@tag('cui')
 class MiddlewareTests(InvenTreeTestCase):
     """Test for middleware functions."""
 
diff --git a/InvenTree/InvenTree/test_urls.py b/InvenTree/InvenTree/test_urls.py
index d15493a750..42744e0a16 100644
--- a/InvenTree/InvenTree/test_urls.py
+++ b/InvenTree/InvenTree/test_urls.py
@@ -4,10 +4,11 @@ import os
 import re
 from pathlib import Path
 
-from django.test import TestCase
+from django.test import TestCase, tag
 from django.urls import reverse
 
 
+@tag('cui')
 class URLTest(TestCase):
     """Test all files for broken url tags."""
 
diff --git a/InvenTree/InvenTree/test_views.py b/InvenTree/InvenTree/test_views.py
index 8bbfa7ffc9..0ad1ae5e45 100644
--- a/InvenTree/InvenTree/test_views.py
+++ b/InvenTree/InvenTree/test_views.py
@@ -3,6 +3,7 @@
 import os
 
 from django.contrib.auth import get_user_model
+from django.test import tag
 from django.urls import reverse
 
 from InvenTree.unit_test import InvenTreeTestCase
@@ -35,6 +36,7 @@ class ViewTests(InvenTreeTestCase):
 
         return str(response.content.decode())
 
+    @tag('cui')
     def test_panels(self):
         """Test that the required 'panels' are present."""
         content = self.get_index_page()
@@ -43,6 +45,7 @@ class ViewTests(InvenTreeTestCase):
 
         # TODO: In future, run the javascript and ensure that the panels get created!
 
+    @tag('cui')
     def test_settings_page(self):
         """Test that the 'settings' page loads correctly."""
         # Settings page loads
@@ -101,6 +104,8 @@ class ViewTests(InvenTreeTestCase):
             self.assertNotIn(f'select-{panel}', content)
             self.assertNotIn(f'panel-{panel}', content)
 
+    # TODO: Replace this with a PUI test
+    @tag('cui')
     def test_url_login(self):
         """Test logging in via arguments."""
         # Log out
diff --git a/InvenTree/InvenTree/tests.py b/InvenTree/InvenTree/tests.py
index 0556a46e80..4ffc2c0515 100644
--- a/InvenTree/InvenTree/tests.py
+++ b/InvenTree/InvenTree/tests.py
@@ -12,7 +12,7 @@ from django.conf import settings
 from django.contrib.auth import get_user_model
 from django.core import mail
 from django.core.exceptions import ValidationError
-from django.test import TestCase, override_settings
+from django.test import TestCase, override_settings, tag
 from django.urls import reverse
 
 import pint.errors
@@ -1319,6 +1319,8 @@ class MagicLoginTest(InvenTreeTestCase):
         self.assertEqual(resp.wsgi_request.user, self.user)
 
 
+# TODO - refactor to not use CUI
+@tag('cui')
 class MaintenanceModeTest(InvenTreeTestCase):
     """Unit tests for maintenance mode."""
 
diff --git a/InvenTree/InvenTree/urls.py b/InvenTree/InvenTree/urls.py
index 4711bc1652..6f007a084a 100644
--- a/InvenTree/InvenTree/urls.py
+++ b/InvenTree/InvenTree/urls.py
@@ -362,15 +362,19 @@ translated_javascript_urls = [
 ]
 
 backendpatterns = [
-    # "Dynamic" javascript files which are rendered using InvenTree templating.
-    path('js/dynamic/', include(dynamic_javascript_urls)),
-    path('js/i18n/', include(translated_javascript_urls)),
     path('auth/', include('rest_framework.urls', namespace='rest_framework')),
     path('auth/', auth_request),
     path('api/', include(apipatterns)),
     path('api-doc/', SpectacularRedocView.as_view(url_name='schema'), name='api-doc'),
 ]
 
+if settings.ENABLE_CLASSIC_FRONTEND:
+    # "Dynamic" javascript files which are rendered using InvenTree templating.
+    backendpatterns += [
+        re_path(r'^js/dynamic/', include(dynamic_javascript_urls)),
+        re_path(r'^js/i18n/', include(translated_javascript_urls)),
+    ]
+
 classic_frontendpatterns = [
     # Apps
     path('build/', include(build_urls)),
@@ -436,6 +440,15 @@ if settings.ENABLE_CLASSIC_FRONTEND:
     frontendpatterns += classic_frontendpatterns
 if settings.ENABLE_PLATFORM_FRONTEND:
     frontendpatterns += platform_urls
+    if not settings.ENABLE_CLASSIC_FRONTEND:
+        # Add a redirect for login views
+        frontendpatterns += [
+            path(
+                'accounts/login/',
+                RedirectView.as_view(url=settings.FRONTEND_URL_BASE, permanent=False),
+                name='account_login',
+            )
+        ]
 
 urlpatterns += frontendpatterns
 
@@ -461,5 +474,14 @@ urlpatterns.append(
 
 # Send any unknown URLs to the parts page
 urlpatterns += [
-    re_path(r'^.*$', RedirectView.as_view(url='/index/', permanent=False), name='index')
+    re_path(
+        r'^.*$',
+        RedirectView.as_view(
+            url='/index/'
+            if settings.ENABLE_CLASSIC_FRONTEND
+            else settings.FRONTEND_URL_BASE,
+            permanent=False,
+        ),
+        name='index',
+    )
 ]
diff --git a/InvenTree/build/models.py b/InvenTree/build/models.py
index 72f87e87fc..ca3b202317 100644
--- a/InvenTree/build/models.py
+++ b/InvenTree/build/models.py
@@ -4,6 +4,7 @@ import decimal
 import logging
 import os
 from datetime import datetime
+from django.conf import settings
 
 from django.contrib.auth.models import User
 from django.core.exceptions import ValidationError
@@ -161,7 +162,9 @@ class Build(InvenTree.models.InvenTreeBarcodeMixin, InvenTree.models.InvenTreeNo
 
     def get_absolute_url(self):
         """Return the web URL associated with this BuildOrder"""
-        return reverse('build-detail', kwargs={'pk': self.id})
+        if settings.ENABLE_CLASSIC_FRONTEND:
+            return reverse('build-detail', kwargs={'pk': self.id})
+        return InvenTree.helpers.pui_url(f'/build/{self.id}')
 
     reference = models.CharField(
         unique=True,
diff --git a/InvenTree/build/tests.py b/InvenTree/build/tests.py
index 196701729b..904f2a3a62 100644
--- a/InvenTree/build/tests.py
+++ b/InvenTree/build/tests.py
@@ -1,5 +1,7 @@
 """Basic unit tests for the BuildOrder app"""
 
+from django.conf import settings
+from django.test import tag
 from django.urls import reverse
 
 from datetime import datetime, timedelta
@@ -40,7 +42,8 @@ class BuildTestSimple(InvenTreeTestCase):
     def test_url(self):
         """Test URL lookup"""
         b1 = Build.objects.get(pk=1)
-        self.assertEqual(b1.get_absolute_url(), '/build/1/')
+        if settings.ENABLE_CLASSIC_FRONTEND:
+            self.assertEqual(b1.get_absolute_url(), '/build/1/')
 
     def test_is_complete(self):
         """Test build completion status"""
@@ -116,11 +119,13 @@ class TestBuildViews(InvenTreeTestCase):
             is_building=True,
         )
 
+    @tag('cui')
     def test_build_index(self):
         """Test build index view."""
         response = self.client.get(reverse('build-index'))
         self.assertEqual(response.status_code, 200)
 
+    @tag('cui')
     def test_build_detail(self):
         """Test the detail view for a Build object."""
         pk = 1
diff --git a/InvenTree/company/models.py b/InvenTree/company/models.py
index 62d9af03b7..f8cdedcc8b 100644
--- a/InvenTree/company/models.py
+++ b/InvenTree/company/models.py
@@ -5,6 +5,7 @@ from datetime import datetime
 from decimal import Decimal
 
 from django.apps import apps
+from django.conf import settings
 from django.core.exceptions import ValidationError
 from django.core.validators import MinValueValidator
 from django.db import models
@@ -216,7 +217,9 @@ class Company(
 
     def get_absolute_url(self):
         """Get the web URL for the detail view for this Company."""
-        return reverse('company-detail', kwargs={'pk': self.id})
+        if settings.ENABLE_CLASSIC_FRONTEND:
+            return reverse('company-detail', kwargs={'pk': self.id})
+        return InvenTree.helpers.pui_url(f'/company/{self.id}')
 
     def get_image_url(self):
         """Return the URL of the image for this company."""
@@ -683,7 +686,9 @@ class SupplierPart(
 
     def get_absolute_url(self):
         """Return the web URL of the detail view for this SupplierPart."""
-        return reverse('supplier-part-detail', kwargs={'pk': self.id})
+        if settings.ENABLE_CLASSIC_FRONTEND:
+            return reverse('supplier-part-detail', kwargs={'pk': self.id})
+        return InvenTree.helpers.pui_url(f'/purchasing/supplier-part/{self.id}')
 
     def api_instance_filters(self):
         """Return custom API filters for this particular instance."""
diff --git a/InvenTree/company/test_views.py b/InvenTree/company/test_views.py
index d35e0662b2..7504ebf0f2 100644
--- a/InvenTree/company/test_views.py
+++ b/InvenTree/company/test_views.py
@@ -1,10 +1,12 @@
 """Unit tests for Company views (see views.py)."""
 
+from django.test import tag
 from django.urls import reverse
 
 from InvenTree.unit_test import InvenTreeTestCase
 
 
+@tag('cui')
 class CompanyViewTest(InvenTreeTestCase):
     """Tests for various 'Company' views."""
 
diff --git a/InvenTree/company/tests.py b/InvenTree/company/tests.py
index 9496fc278f..a789b951d4 100644
--- a/InvenTree/company/tests.py
+++ b/InvenTree/company/tests.py
@@ -3,6 +3,7 @@
 import os
 from decimal import Decimal
 
+from django.conf import settings
 from django.core.exceptions import ValidationError
 from django.test import TestCase
 
@@ -59,7 +60,8 @@ class CompanySimpleTest(TestCase):
     def test_company_url(self):
         """Test the detail URL for a company."""
         c = Company.objects.get(pk=1)
-        self.assertEqual(c.get_absolute_url(), '/company/1/')
+        if settings.ENABLE_CLASSIC_FRONTEND:
+            self.assertEqual(c.get_absolute_url(), '/company/1/')
 
     def test_image_renamer(self):
         """Test the company image upload functionality."""
diff --git a/InvenTree/label/tests.py b/InvenTree/label/tests.py
index b4a66b6f4d..08669c338d 100644
--- a/InvenTree/label/tests.py
+++ b/InvenTree/label/tests.py
@@ -142,7 +142,8 @@ class LabelTest(InvenTreeAPITestCase):
         # Test that each element has been rendered correctly
         self.assertIn(f'part: {part_pk} - {part_name}', content)
         self.assertIn(f'data: {{"part": {part_pk}}}', content)
-        self.assertIn(f'http://testserver/part/{part_pk}/', content)
+        if settings.ENABLE_CLASSIC_FRONTEND:
+            self.assertIn(f'http://testserver/part/{part_pk}/', content)
 
         # Check that a encoded image has been generated
         self.assertIn('data:image/png;charset=utf-8;base64,', content)
diff --git a/InvenTree/order/models.py b/InvenTree/order/models.py
index 80ac2f616c..ca550b19f9 100644
--- a/InvenTree/order/models.py
+++ b/InvenTree/order/models.py
@@ -6,6 +6,7 @@ import sys
 from datetime import datetime
 from decimal import Decimal
 
+from django.conf import settings
 from django.contrib.auth.models import User
 from django.core.exceptions import ValidationError
 from django.core.validators import MinValueValidator
@@ -41,7 +42,7 @@ from InvenTree.fields import (
     InvenTreeURLField,
     RoundingDecimalField,
 )
-from InvenTree.helpers import decimal2string
+from InvenTree.helpers import decimal2string, pui_url
 from InvenTree.helpers_model import getSetting, notify_responsible
 from InvenTree.status_codes import (
     PurchaseOrderStatus,
@@ -348,7 +349,9 @@ class PurchaseOrder(TotalPriceMixin, Order):
 
     def get_absolute_url(self):
         """Get the 'web' URL for this order."""
-        return reverse('po-detail', kwargs={'pk': self.pk})
+        if settings.ENABLE_CLASSIC_FRONTEND:
+            return reverse('po-detail', kwargs={'pk': self.pk})
+        return pui_url(f'/purchasing/purchase-order/{self.pk}')
 
     @staticmethod
     def get_api_url():
@@ -804,7 +807,9 @@ class SalesOrder(TotalPriceMixin, Order):
 
     def get_absolute_url(self):
         """Get the 'web' URL for this order."""
-        return reverse('so-detail', kwargs={'pk': self.pk})
+        if settings.ENABLE_CLASSIC_FRONTEND:
+            return reverse('so-detail', kwargs={'pk': self.pk})
+        return pui_url(f'/sales/sales-order/{self.pk}')
 
     @staticmethod
     def get_api_url():
@@ -1940,7 +1945,9 @@ class ReturnOrder(TotalPriceMixin, Order):
 
     def get_absolute_url(self):
         """Get the 'web' URL for this order."""
-        return reverse('return-order-detail', kwargs={'pk': self.pk})
+        if settings.ENABLE_CLASSIC_FRONTEND:
+            return reverse('return-order-detail', kwargs={'pk': self.pk})
+        return pui_url(f'/sales/return-order/{self.pk}')
 
     @staticmethod
     def get_api_url():
diff --git a/InvenTree/order/test_views.py b/InvenTree/order/test_views.py
index 825495daa4..d0a6cfce0f 100644
--- a/InvenTree/order/test_views.py
+++ b/InvenTree/order/test_views.py
@@ -1,5 +1,6 @@
 """Unit tests for Order views (see views.py)."""
 
+from django.test import tag
 from django.urls import reverse
 
 from InvenTree.unit_test import InvenTreeTestCase
@@ -34,6 +35,7 @@ class OrderViewTestCase(InvenTreeTestCase):
     ]
 
 
+@tag('cui')
 class PurchaseOrderListTest(OrderViewTestCase):
     """Unit tests for the PurchaseOrder index page."""
 
@@ -44,6 +46,7 @@ class PurchaseOrderListTest(OrderViewTestCase):
         self.assertEqual(response.status_code, 200)
 
 
+@tag('cui')
 class PurchaseOrderTests(OrderViewTestCase):
     """Tests for PurchaseOrder views."""
 
@@ -65,6 +68,7 @@ class PurchaseOrderTests(OrderViewTestCase):
         self.assertIn('streaming_content', dir(response))
 
 
+@tag('cui')
 class SalesOrderViews(OrderViewTestCase):
     """Unit tests for the SalesOrder pages."""
 
@@ -79,6 +83,7 @@ class SalesOrderViews(OrderViewTestCase):
         self.assertEqual(response.status_code, 200)
 
 
+@tag('cui')
 class ReturnOrderVIews(OrderViewTestCase):
     """Unit tests for the ReturnOrder pages."""
 
diff --git a/InvenTree/order/tests.py b/InvenTree/order/tests.py
index c3a22f71ef..1b6cdb9081 100644
--- a/InvenTree/order/tests.py
+++ b/InvenTree/order/tests.py
@@ -4,6 +4,7 @@ from datetime import datetime, timedelta
 from decimal import Decimal
 
 import django.core.exceptions as django_exceptions
+from django.conf import settings
 from django.contrib.auth import get_user_model
 from django.contrib.auth.models import Group
 from django.test import TestCase
@@ -41,7 +42,10 @@ class OrderTest(TestCase):
         for pk in range(1, 8):
             order = PurchaseOrder.objects.get(pk=pk)
 
-            self.assertEqual(order.get_absolute_url(), f'/order/purchase-order/{pk}/')
+            if settings.ENABLE_CLASSIC_FRONTEND:
+                self.assertEqual(
+                    order.get_absolute_url(), f'/order/purchase-order/{pk}/'
+                )
 
             self.assertEqual(order.reference, f'PO-{pk:04d}')
 
diff --git a/InvenTree/part/models.py b/InvenTree/part/models.py
index b07c320626..c962ba3d81 100644
--- a/InvenTree/part/models.py
+++ b/InvenTree/part/models.py
@@ -132,7 +132,9 @@ class PartCategory(InvenTree.models.InvenTreeTree):
 
     def get_absolute_url(self):
         """Return the web URL associated with the detail view for this PartCategory instance."""
-        return reverse('category-detail', kwargs={'pk': self.id})
+        if settings.ENABLE_CLASSIC_FRONTEND:
+            return reverse('category-detail', kwargs={'pk': self.id})
+        return helpers.pui_url(f'/part/category/{self.id}')
 
     def clean(self):
         """Custom clean action for the PartCategory model.
@@ -754,7 +756,9 @@ class Part(
 
     def get_absolute_url(self):
         """Return the web URL for viewing this part."""
-        return reverse('part-detail', kwargs={'pk': self.id})
+        if settings.ENABLE_CLASSIC_FRONTEND:
+            return reverse('part-detail', kwargs={'pk': self.id})
+        return helpers.pui_url(f'/part/{self.id}')
 
     def get_image_url(self):
         """Return the URL of the image for this part."""
diff --git a/InvenTree/part/test_category.py b/InvenTree/part/test_category.py
index d12b2f9fee..d3e89994ab 100644
--- a/InvenTree/part/test_category.py
+++ b/InvenTree/part/test_category.py
@@ -1,5 +1,6 @@
 """Unit tests for the PartCategory model."""
 
+from django.conf import settings
 from django.core.exceptions import ValidationError
 from django.test import TestCase
 
@@ -125,7 +126,8 @@ class CategoryTest(TestCase):
 
     def test_url(self):
         """Test that the PartCategory URL works."""
-        self.assertEqual(self.capacitors.get_absolute_url(), '/part/category/3/')
+        if settings.ENABLE_CLASSIC_FRONTEND:
+            self.assertEqual(self.capacitors.get_absolute_url(), '/part/category/3/')
 
     def test_part_count(self):
         """Test that the Category part count works."""
diff --git a/InvenTree/part/test_part.py b/InvenTree/part/test_part.py
index 3523e8e50b..c267043855 100644
--- a/InvenTree/part/test_part.py
+++ b/InvenTree/part/test_part.py
@@ -237,7 +237,8 @@ class PartTest(TestCase):
     def test_attributes(self):
         """Test Part attributes."""
         self.assertEqual(self.r1.name, 'R_2K2_0805')
-        self.assertEqual(self.r1.get_absolute_url(), '/part/3/')
+        if settings.ENABLE_CLASSIC_FRONTEND:
+            self.assertEqual(self.r1.get_absolute_url(), '/part/3/')
 
     def test_category(self):
         """Test PartCategory path."""
diff --git a/InvenTree/part/test_views.py b/InvenTree/part/test_views.py
index c96e675405..0fe44c09ec 100644
--- a/InvenTree/part/test_views.py
+++ b/InvenTree/part/test_views.py
@@ -1,5 +1,6 @@
 """Unit tests for Part Views (see views.py)."""
 
+from django.test import tag
 from django.urls import reverse
 
 from InvenTree.unit_test import InvenTreeTestCase
@@ -16,6 +17,7 @@ class PartViewTestCase(InvenTreeTestCase):
     superuser = True
 
 
+@tag('cui')
 class PartListTest(PartViewTestCase):
     """Unit tests for the PartList view."""
 
@@ -33,6 +35,7 @@ class PartListTest(PartViewTestCase):
 class PartDetailTest(PartViewTestCase):
     """Unit tests for the PartDetail view."""
 
+    @tag('cui')
     def test_part_detail(self):
         """Test that we can retrieve a part detail page."""
         pk = 1
@@ -50,6 +53,7 @@ class PartDetailTest(PartViewTestCase):
         self.assertEqual(response.context['part'].pk, pk)
         self.assertEqual(response.context['category'], part.category)
 
+    @tag('cui')
     def test_part_detail_from_ipn(self):
         """Test that we can retrieve a part detail page from part IPN.
 
diff --git a/InvenTree/plugin/base/integration/test_mixins.py b/InvenTree/plugin/base/integration/test_mixins.py
index ebee35999d..0cba7f5854 100644
--- a/InvenTree/plugin/base/integration/test_mixins.py
+++ b/InvenTree/plugin/base/integration/test_mixins.py
@@ -3,7 +3,7 @@
 import os
 
 from django.conf import settings
-from django.test import TestCase
+from django.test import TestCase, tag
 from django.urls import include, path, re_path, reverse
 
 from error_report.models import Error
@@ -363,6 +363,7 @@ class PanelMixinTests(InvenTreeTestCase):
         plugins = registry.with_mixin('panel', active=False)
         self.assertEqual(len(plugins), 0)
 
+    @tag('cui')
     def test_disabled(self):
         """Test that the panels *do not load* if the plugin is not enabled."""
         plugin = registry.get_plugin('samplepanel')
@@ -390,6 +391,7 @@ class PanelMixinTests(InvenTreeTestCase):
             self.assertNotIn('Hello world', str(response.content))
             self.assertNotIn('Custom Part Panel', str(response.content))
 
+    @tag('cui')
     def test_enabled(self):
         """Test that the panels *do* load if the plugin is enabled."""
         plugin = registry.get_plugin('samplepanel')
diff --git a/InvenTree/plugin/builtin/barcodes/test_inventree_barcode.py b/InvenTree/plugin/builtin/barcodes/test_inventree_barcode.py
index 98a6aa30d2..4f5a4b405c 100644
--- a/InvenTree/plugin/builtin/barcodes/test_inventree_barcode.py
+++ b/InvenTree/plugin/builtin/barcodes/test_inventree_barcode.py
@@ -1,5 +1,6 @@
 """Unit tests for InvenTreeBarcodePlugin."""
 
+from django.conf import settings
 from django.urls import reverse
 
 import part.models
@@ -270,9 +271,10 @@ class TestInvenTreeBarcode(InvenTreeAPITestCase):
         self.assertEqual(
             response.data['stocklocation']['api_url'], '/api/stock/location/5/'
         )
-        self.assertEqual(
-            response.data['stocklocation']['web_url'], '/stock/location/5/'
-        )
+        if settings.ENABLE_CLASSIC_FRONTEND:
+            self.assertEqual(
+                response.data['stocklocation']['web_url'], '/stock/location/5/'
+            )
         self.assertEqual(response.data['plugin'], 'InvenTreeBarcode')
 
         # Scan a Part object
diff --git a/InvenTree/plugin/plugin.py b/InvenTree/plugin/plugin.py
index 339a2d4f01..5b70ac49e1 100644
--- a/InvenTree/plugin/plugin.py
+++ b/InvenTree/plugin/plugin.py
@@ -13,6 +13,7 @@ from django.urls.base import reverse
 from django.utils.text import slugify
 from django.utils.translation import gettext_lazy as _
 
+from InvenTree.helpers import pui_url
 from plugin.helpers import get_git_log
 
 logger = logging.getLogger('inventree')
@@ -364,7 +365,13 @@ class InvenTreePlugin(VersionMixin, MixinBase, MetaBase):
     @property
     def settings_url(self):
         """URL to the settings panel for this plugin."""
-        return f'{reverse("settings")}#select-plugin-{self.slug}'
+        if settings.ENABLE_CLASSIC_FRONTEND:
+            return f'{reverse("settings")}#select-plugin-{self.slug}'
+        config = self.plugin_config()
+        if config:
+            return pui_url(f'/settings/admin/plugin/{config.pk}/')
+        else:
+            return pui_url('/settings/admin/plugin/')
 
     # region package info
     def _get_package_commit(self):
diff --git a/InvenTree/stock/models.py b/InvenTree/stock/models.py
index 517345b243..99d3ae9eee 100644
--- a/InvenTree/stock/models.py
+++ b/InvenTree/stock/models.py
@@ -7,6 +7,7 @@ import os
 from datetime import datetime, timedelta
 from decimal import Decimal, InvalidOperation
 
+from django.conf import settings
 from django.contrib.auth.models import User
 from django.core.exceptions import FieldError, ValidationError
 from django.core.validators import MinValueValidator
@@ -258,7 +259,9 @@ class StockLocation(
 
     def get_absolute_url(self):
         """Return url for instance."""
-        return reverse('stock-location-detail', kwargs={'pk': self.id})
+        if settings.ENABLE_CLASSIC_FRONTEND:
+            return reverse('stock-location-detail', kwargs={'pk': self.id})
+        return InvenTree.helpers.pui_url(f'/stock/location/{self.id}')
 
     def get_stock_items(self, cascade=True):
         """Return a queryset for all stock items under this category.
@@ -727,7 +730,9 @@ class StockItem(
 
     def get_absolute_url(self):
         """Return url for instance."""
-        return reverse('stock-item-detail', kwargs={'pk': self.id})
+        if settings.ENABLE_CLASSIC_FRONTEND:
+            return reverse('stock-item-detail', kwargs={'pk': self.id})
+        return InvenTree.helpers.pui_url(f'/stock/item/{self.id}')
 
     def get_part_name(self):
         """Returns part name."""
@@ -2304,7 +2309,9 @@ class StockItemTracking(InvenTree.models.InvenTreeModel):
 
     def get_absolute_url(self):
         """Return url for instance."""
-        return f'/stock/track/{self.id}'
+        if settings.ENABLE_CLASSIC_FRONTEND:
+            return f'/stock/track/{self.id}'
+        return InvenTree.helpers.pui_url(f'/stock/item/{self.item.id}')
 
     def label(self):
         """Return label."""
diff --git a/InvenTree/stock/test_views.py b/InvenTree/stock/test_views.py
index 2d2167f012..e6bb420636 100644
--- a/InvenTree/stock/test_views.py
+++ b/InvenTree/stock/test_views.py
@@ -1,6 +1,7 @@
 """Unit tests for Stock views (see views.py)."""
 
 from django.contrib.auth.models import Group
+from django.test import tag
 from django.urls import reverse
 
 from common.models import InvenTreeSetting
@@ -18,6 +19,7 @@ class StockViewTestCase(InvenTreeTestCase):
     roles = 'all'
 
 
+@tag('cui')
 class StockListTest(StockViewTestCase):
     """Tests for Stock list views."""
 
@@ -30,6 +32,7 @@ class StockListTest(StockViewTestCase):
 class StockDetailTest(StockViewTestCase):
     """Unit test for the 'stock detail' page."""
 
+    @tag('cui')
     def test_basic_info(self):
         """Test that basic stock item info is rendered."""
         url = reverse('stock-item-detail', kwargs={'pk': 1})
diff --git a/InvenTree/stock/tests.py b/InvenTree/stock/tests.py
index a9a6e1cfda..89d9e8e9d2 100644
--- a/InvenTree/stock/tests.py
+++ b/InvenTree/stock/tests.py
@@ -2,6 +2,7 @@
 
 import datetime
 
+from django.conf import settings
 from django.core.exceptions import ValidationError
 from django.db.models import Sum
 from django.test import override_settings
@@ -256,9 +257,9 @@ class StockTest(StockTestBase):
     def test_url(self):
         """Test get_absolute_url function."""
         it = StockItem.objects.get(pk=2)
-        self.assertEqual(it.get_absolute_url(), '/stock/item/2/')
-
-        self.assertEqual(self.home.get_absolute_url(), '/stock/location/1/')
+        if settings.ENABLE_CLASSIC_FRONTEND:
+            self.assertEqual(it.get_absolute_url(), '/stock/item/2/')
+            self.assertEqual(self.home.get_absolute_url(), '/stock/location/1/')
 
     def test_strings(self):
         """Test str function."""
diff --git a/InvenTree/users/tests.py b/InvenTree/users/tests.py
index 4e29bf0497..84f0cd1668 100644
--- a/InvenTree/users/tests.py
+++ b/InvenTree/users/tests.py
@@ -2,7 +2,7 @@
 
 from django.apps import apps
 from django.contrib.auth.models import Group
-from django.test import TestCase
+from django.test import TestCase, tag
 from django.urls import reverse
 
 from InvenTree.unit_test import InvenTreeTestCase
@@ -165,6 +165,8 @@ class OwnerModelTest(InvenTreeTestCase):
         self.assertEqual(response.status_code, status_code)
         return response.data
 
+    # TODO: Find out why this depends on CUI
+    @tag('cui')
     def test_owner(self):
         """Tests for the 'owner' model."""
         # Check that owner was created for user
diff --git a/tasks.py b/tasks.py
index 49cb3db158..e7d09d1605 100644
--- a/tasks.py
+++ b/tasks.py
@@ -804,10 +804,17 @@ def test_translations(c):
         'migrations': 'Run migration unit tests',
         'report': 'Display a report of slow tests',
         'coverage': 'Run code coverage analysis (requires coverage package)',
+        'cui': 'Do not run CUI tests',
     }
 )
 def test(
-    c, disable_pty=False, runtest='', migrations=False, report=False, coverage=False
+    c,
+    disable_pty=False,
+    runtest='',
+    migrations=False,
+    report=False,
+    coverage=False,
+    cui=False,
 ):
     """Run unit-tests for InvenTree codebase.
 
@@ -843,6 +850,9 @@ def test(
     else:
         cmd += ' --exclude-tag migration_test'
 
+    if cui:
+        cmd += ' --exclude-tag=cui'
+
     if coverage:
         # Run tests within coverage environment, and generate report
         c.run(f'coverage run {managePyPath()} {cmd}')