diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index fef3a2b3de1da94c1aa11cb16cc00277cd7e5ebd..92f01952d3494811d8292cef9c579330ac1e7f76 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -9,6 +9,11 @@ and this project adheres to `Semantic Versioning`_.
 Unreleased
 ----------
 
+Changed
+~~~~~~~
+
+* Official apps can now override any setting
+
 `2.8`_ - 2022-03-11
 -------------------
 
diff --git a/aleksis/core/settings.py b/aleksis/core/settings.py
index 966adca96ca4d34a98510185c925433e54021cb7..e329e9bc69ae795aae7c125cd31f69bd5e1ced98 100644
--- a/aleksis/core/settings.py
+++ b/aleksis/core/settings.py
@@ -7,7 +7,12 @@ from django.utils.translation import gettext_lazy as _
 
 from dynaconf import LazySettings
 
-from .util.core_helpers import get_app_packages, merge_app_settings, monkey_patch
+from .util.core_helpers import (
+    get_app_packages,
+    get_app_settings_overrides,
+    merge_app_settings,
+    monkey_patch,
+)
 
 monkey_patch()
 
@@ -1007,3 +1012,5 @@ merge_app_settings("SHELL_PLUS_DONT_LOAD", SHELL_PLUS_DONT_LOAD)
 
 # Add django-cleanup after all apps to ensure that it gets all signals as last app
 INSTALLED_APPS.append("django_cleanup.apps.CleanupConfig")
+
+locals().update(get_app_settings_overrides())
diff --git a/aleksis/core/util/core_helpers.py b/aleksis/core/util/core_helpers.py
index 6988dd53137f2d5ca364bcfd7091d9b1795e7f10..6c9e0f10f0009f4a435c07e7f332605fda7a3e85 100644
--- a/aleksis/core/util/core_helpers.py
+++ b/aleksis/core/util/core_helpers.py
@@ -3,6 +3,7 @@ from datetime import datetime, timedelta
 from importlib import import_module, metadata
 from itertools import groupby
 from operator import itemgetter
+from types import ModuleType
 from typing import Any, Callable, Dict, Optional, Sequence, Union
 from warnings import warn
 
@@ -59,9 +60,31 @@ def dt_show_toolbar(request: HttpRequest) -> bool:
     return False
 
 
-def get_app_packages() -> Sequence[str]:
+def get_app_packages(only_official: bool = False) -> Sequence[str]:
     """Find all registered apps from the setuptools entrypoint."""
-    return [f"{ep.module}.{ep.attr}" for ep in metadata.entry_points().get("aleksis.app", [])]
+    apps = []
+
+    for ep in metadata.entry_points().get("aleksis.app", []):
+        path = f"{ep.module}.{ep.attr}"
+        if path.startswith("aleksis.apps.") or not only_official:
+            apps.append(path)
+
+    return apps
+
+
+def get_app_settings_module(app: str) -> Optional[ModuleType]:
+    """Get the settings module of an app."""
+    pkg = ".".join(app.split(".")[:-2])
+    mod_settings = None
+    while "." in pkg:
+        try:
+            return import_module(pkg + ".settings")
+        except ImportError:
+            # Import errors are non-fatal.
+            pkg = ".".join(pkg.split(".")[:-1])
+
+    # The app does not have settings
+    return None
 
 
 def merge_app_settings(
@@ -77,18 +100,8 @@ def merge_app_settings(
     potentially malicious apps!
     """
     for app in get_app_packages():
-        pkg = ".".join(app.split(".")[:-2])
-        mod_settings = None
-        while "." in pkg:
-            try:
-                mod_settings = import_module(pkg + ".settings")
-            except ImportError:
-                # Import errors are non-fatal.
-                pkg = ".".join(pkg.split(".")[:-1])
-                continue
-            break
+        mod_settings = get_app_settings_module(app)
         if not mod_settings:
-            # The app does not have settings
             continue
 
         app_setting = getattr(mod_settings, setting, None)
@@ -109,6 +122,26 @@ def merge_app_settings(
                     raise TypeError("Only dict and list settings can be merged.")
 
 
+def get_app_settings_overrides() -> dict[str, Any]:
+    """Get app settings overrides.
+
+    Official apps (those under the ``aleksis.apps` namespace) can override
+    or add settings by listing them in their ``settings.overrides``.
+    """
+    overrides = {}
+
+    for app in get_app_packages(True):
+        mod_settings = get_app_settings_module(app)
+        if not mod_settings:
+            continue
+
+        if hasattr(mod_settings, "overrides"):
+            for name in mod_settings.overrides:
+                overrides[name] = getattr(mod_settings, name)
+
+    return overrides
+
+
 def get_site_preferences():
     """Get the preferences manager of the current site."""
     from django.contrib.sites.models import Site  # noqa
diff --git a/docs/dev/06_merging_app_settings.rst b/docs/dev/06_merging_app_settings.rst
index ab24e91138cb7ecb646cd5c299a1f8b9833ed8f7..3c0c1affd210c706ac2959d02324f6ad9e009a2b 100644
--- a/docs/dev/06_merging_app_settings.rst
+++ b/docs/dev/06_merging_app_settings.rst
@@ -3,9 +3,15 @@ Merging of app settings
 
 AlekSIS provides features to merge app settings into main ``settings.py``.
 
+Third-party apps can only add values to some select existing settings.
+Official apps (those under the ``aleksis.apps.`` namespace) can mark any
+setting for overriding.
+
 Currently mergable settings
 ---------------------------
 
+The following settings can be amended by any app:
+
  * INSTALLED_APPS
  * DATABASES
  * YARN_INSTALLED_APPS
@@ -24,3 +30,13 @@ the following into your ``settings.py``::
             "PORT": 5432,
         }
     }
+
+Overriding any setting
+----------------------
+
+Official apps only (currently) can override any setting, but need to explicitly
+mark it by listing it in a list called ``overrides`` in their ``settings.py``::
+
+    PAYMENT_MODEL = "tezor.Invoice"
+
+    overrides = ["PAYMENT_MODEL"]