diff --git a/pdm.lock b/pdm.lock
index e3e25e025e850b520b70d2d5db20131784f54b68..80fa92196c5cf368447c3c03aec97b0f53e7aa64 100644
--- a/pdm.lock
+++ b/pdm.lock
@@ -2,10 +2,10 @@
 # It is not intended for manual editing.
 
 [metadata]
-groups = ["default", "dev", "lint", "local", "static-analysis", "typing"]
+groups = ["default", "dev", "lint", "local", "static-analysis", "typing", "watchfiles"]
 strategy = ["inherit_metadata"]
 lock_version = "4.5.0"
-content_hash = "sha256:eb464da9ea46d32eafba4a0e403be2151ff6c43f9045e8b6a9633e3de26370ef"
+content_hash = "sha256:fc86b394f3b33fdd867e661094a00a1b8cbb93ae926d2d1e8b1590fa54771159"
 
 [[metadata.targets]]
 requires_python = "==3.13.*"
@@ -29,7 +29,7 @@ name = "anyio"
 version = "4.6.2.post1"
 requires_python = ">=3.9"
 summary = "High level compatibility layer for multiple asynchronous event loop implementations"
-groups = ["default"]
+groups = ["default", "watchfiles"]
 dependencies = [
     "exceptiongroup>=1.0.2; python_version < \"3.11\"",
     "idna>=2.8",
@@ -46,7 +46,7 @@ name = "asgiref"
 version = "3.8.1"
 requires_python = ">=3.8"
 summary = "ASGI specs, helper code, and adapters"
-groups = ["default", "typing"]
+groups = ["default", "typing", "watchfiles"]
 dependencies = [
     "typing-extensions>=4; python_version < \"3.11\"",
 ]
@@ -432,7 +432,7 @@ name = "django"
 version = "5.1.2"
 requires_python = ">=3.10"
 summary = "A high-level Python web framework that encourages rapid development and clean, pragmatic design."
-groups = ["default", "typing"]
+groups = ["default", "typing", "watchfiles"]
 dependencies = [
     "asgiref<4,>=3.8.1",
     "sqlparse>=0.3.1",
@@ -648,6 +648,21 @@ files = [
     {file = "django_timezone_field-7.0.tar.gz", hash = "sha256:aa6f4965838484317b7f08d22c0d91a53d64e7bbbd34264468ae83d4023898a7"},
 ]
 
+[[package]]
+name = "django-watchfiles"
+version = "1.0.0"
+requires_python = ">=3.9"
+summary = "Use watchfiles in Django’s autoreloader."
+groups = ["watchfiles"]
+dependencies = [
+    "django>=4.2",
+    "watchfiles",
+]
+files = [
+    {file = "django_watchfiles-1.0.0-py3-none-any.whl", hash = "sha256:eaee798f908864f2a0b73d0362867c7d3a284398eaca82a0583b43969a37178d"},
+    {file = "django_watchfiles-1.0.0.tar.gz", hash = "sha256:e4aa8e910bee610dae2b586771e587072fd984fad8a17e83940e707cfe2ca427"},
+]
+
 [[package]]
 name = "django-widget-tweaks"
 version = "1.5.0"
@@ -907,7 +922,7 @@ name = "idna"
 version = "3.10"
 requires_python = ">=3.6"
 summary = "Internationalized Domain Names in Applications (IDNA)"
-groups = ["default"]
+groups = ["default", "watchfiles"]
 files = [
     {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"},
     {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"},
@@ -1894,7 +1909,7 @@ name = "sniffio"
 version = "1.3.1"
 requires_python = ">=3.7"
 summary = "Sniff out which async library your code is running under"
-groups = ["default"]
+groups = ["default", "watchfiles"]
 files = [
     {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"},
     {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"},
@@ -1927,7 +1942,7 @@ name = "sqlparse"
 version = "0.5.1"
 requires_python = ">=3.8"
 summary = "A non-validating SQL parser."
-groups = ["default", "typing"]
+groups = ["default", "typing", "watchfiles"]
 files = [
     {file = "sqlparse-0.5.1-py3-none-any.whl", hash = "sha256:773dcbf9a5ab44a090f3441e2180efe2560220203dc2f8c0b0fa141e18b505e4"},
     {file = "sqlparse-0.5.1.tar.gz", hash = "sha256:bb6b4df465655ef332548e24f08e205afc81b9ab86cb1c45657a7ff173a3a00e"},
@@ -2036,7 +2051,7 @@ name = "tzdata"
 version = "2024.2"
 requires_python = ">=2"
 summary = "Provider of IANA time zone data"
-groups = ["default", "typing"]
+groups = ["default", "typing", "watchfiles"]
 files = [
     {file = "tzdata-2024.2-py2.py3-none-any.whl", hash = "sha256:a48093786cdcde33cad18c2555e8532f34422074448fbc874186f0abd79565cd"},
     {file = "tzdata-2024.2.tar.gz", hash = "sha256:7d85cc416e9382e69095b7bdf4afd9e3880418a2413feec7069d533d6b4e31cc"},
@@ -2085,6 +2100,31 @@ files = [
     {file = "virtualenv-20.27.1.tar.gz", hash = "sha256:142c6be10212543b32c6c45d3d3893dff89112cc588b7d0879ae5a1ec03a47ba"},
 ]
 
+[[package]]
+name = "watchfiles"
+version = "1.0.0"
+requires_python = ">=3.9"
+summary = "Simple, modern and high performance file watching and code reload in python."
+groups = ["watchfiles"]
+dependencies = [
+    "anyio>=3.0.0",
+]
+files = [
+    {file = "watchfiles-1.0.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:95de85c254f7fe8cbdf104731f7f87f7f73ae229493bebca3722583160e6b152"},
+    {file = "watchfiles-1.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:533a7cbfe700e09780bb31c06189e39c65f06c7f447326fee707fd02f9a6e945"},
+    {file = "watchfiles-1.0.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a2218e78e2c6c07b1634a550095ac2a429026b2d5cbcd49a594f893f2bb8c936"},
+    {file = "watchfiles-1.0.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9122b8fdadc5b341315d255ab51d04893f417df4e6c1743b0aac8bf34e96e025"},
+    {file = "watchfiles-1.0.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9272fdbc0e9870dac3b505bce1466d386b4d8d6d2bacf405e603108d50446940"},
+    {file = "watchfiles-1.0.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4a3b33c3aefe9067ebd87846806cd5fc0b017ab70d628aaff077ab9abf4d06b3"},
+    {file = "watchfiles-1.0.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bc338ce9f8846543d428260fa0f9a716626963148edc937d71055d01d81e1525"},
+    {file = "watchfiles-1.0.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ac778a460ea22d63c7e6fb0bc0f5b16780ff0b128f7f06e57aaec63bd339285"},
+    {file = "watchfiles-1.0.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:53ae447f06f8f29f5ab40140f19abdab822387a7c426a369eb42184b021e97eb"},
+    {file = "watchfiles-1.0.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:1f73c2147a453315d672c1ad907abe6d40324e34a185b51e15624bc793f93cc6"},
+    {file = "watchfiles-1.0.0-cp313-none-win32.whl", hash = "sha256:eba98901a2eab909dbd79681190b9049acc650f6111fde1845484a4450761e98"},
+    {file = "watchfiles-1.0.0-cp313-none-win_amd64.whl", hash = "sha256:d562a6114ddafb09c33246c6ace7effa71ca4b6a2324a47f4b09b6445ea78941"},
+    {file = "watchfiles-1.0.0.tar.gz", hash = "sha256:37566c844c9ce3b5deb964fe1a23378e575e74b114618d211fbda8f59d7b5dab"},
+]
+
 [[package]]
 name = "webencodings"
 version = "0.5.1"
diff --git a/pyproject.toml b/pyproject.toml
index 4786eb18c4abf00fe2be17f3fba261c2a953e1e1..f0cafc3d131a8b81c419630b31cf5b7949e5c1f5 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -50,6 +50,10 @@ requires-python = "==3.13.*"
 readme = "README.md"
 license = {text = "MIT"}
 
+[project.optional-dependencies]
+watchfiles = [
+    "django-watchfiles>=1.0.0",
+]
 [tool.pdm]
 distribution = false
 
diff --git a/src/hub/settings/dev.py b/src/hub/settings/dev.py
index 45868760f8ba0f34c55d6d5d3beccce823b9f42c..4537146421af619835b976c267a77fadd37a41a6 100644
--- a/src/hub/settings/dev.py
+++ b/src/hub/settings/dev.py
@@ -1,3 +1,4 @@
+import importlib.util
 import os
 import sys
 
@@ -66,9 +67,7 @@ EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
 
 # configure debug_toolbar if it is installed and we're not testing
 if 'test' not in sys.argv:
-    try:
-        import debug_toolbar  # noqa: F401
-
+    if importlib.util.find_spec('debug_toolbar') is not None:
         INSTALLED_APPS += [  # noqa: F405
             'debug_toolbar',
         ]
@@ -87,17 +86,13 @@ if 'test' not in sys.argv:
         INTERNAL_IPS = [
             '127.0.0.1',
         ]
-
-    except ImportError:
-        pass
+    if importlib.util.find_spec('django_watchfiles') is not None:
+        INSTALLED_APPS += [  # noqa: F405
+            'django_watchfiles',
+        ]
 
 # configure extensions if they're available
 # allows to e.g. generate a PNG of the models' relationships:
 #   ./manage.py graph_models core -g -o ../models.png --disable-abstract-fields -X ContentType,PermissionsMixin,AbstractBaseUser,Group,Permission
-try:
-    import django_extensions  # noqa: F401
-
+if importlib.util.find_spec('django_extensions') is not None:
     INSTALLED_APPS = ['django_extensions', *INSTALLED_APPS]  # noqa: F405
-
-except ImportError:
-    pass