diff --git a/apps/dav/lib/Settings/CalDAVSettings.php b/apps/dav/lib/Settings/CalDAVSettings.php
index 75bb70c530a96291ca2af16dc5aa5a41a7d60821..7c738a83148f0bc19c21b181f421de57e95b155b 100644
--- a/apps/dav/lib/Settings/CalDAVSettings.php
+++ b/apps/dav/lib/Settings/CalDAVSettings.php
@@ -6,6 +6,7 @@
 namespace OCA\DAV\Settings;
 
 use OCA\DAV\AppInfo\Application;
+use OCP\App\IAppManager;
 use OCP\AppFramework\Http\TemplateResponse;
 use OCP\AppFramework\Services\IInitialState;
 use OCP\IConfig;
@@ -21,6 +22,7 @@ class CalDAVSettings implements IDelegatedSettings {
 	private $initialState;
 
 	private IURLGenerator $urlGenerator;
+	private IAppManager $appManager;
 
 	private const defaults = [
 		'sendInvitations' => 'yes',
@@ -36,10 +38,11 @@ class CalDAVSettings implements IDelegatedSettings {
 	 * @param IConfig $config
 	 * @param IInitialState $initialState
 	 */
-	public function __construct(IConfig $config, IInitialState $initialState, IURLGenerator $urlGenerator) {
+	public function __construct(IConfig $config, IInitialState $initialState, IURLGenerator $urlGenerator, IAppManager $appManager) {
 		$this->config = $config;
 		$this->initialState = $initialState;
 		$this->urlGenerator = $urlGenerator;
+		$this->appManager = $appManager;
 	}
 
 	public function getForm(): TemplateResponse {
@@ -51,10 +54,11 @@ class CalDAVSettings implements IDelegatedSettings {
 		return new TemplateResponse(Application::APP_ID, 'settings-admin-caldav');
 	}
 
-	/**
-	 * @return string
-	 */
-	public function getSection() {
+	public function getSection(): ?string {
+		if (!$this->appManager->isBackendRequired(IAppManager::BACKEND_CALDAV)) {
+			return null;
+		}
+
 		return 'groupware';
 	}
 
diff --git a/apps/dav/tests/unit/Settings/CalDAVSettingsTest.php b/apps/dav/tests/unit/Settings/CalDAVSettingsTest.php
index 46a8db6f3eb28a43bb6934c95c24192a88cba6a2..ed497d006e14d755fba90e92a149a95a5655d389 100644
--- a/apps/dav/tests/unit/Settings/CalDAVSettingsTest.php
+++ b/apps/dav/tests/unit/Settings/CalDAVSettingsTest.php
@@ -6,6 +6,7 @@
 namespace OCA\DAV\Tests\Unit\DAV\Settings;
 
 use OCA\DAV\Settings\CalDAVSettings;
+use OCP\App\IAppManager;
 use OCP\AppFramework\Http\TemplateResponse;
 use OCP\AppFramework\Services\IInitialState;
 use OCP\IConfig;
@@ -24,6 +25,9 @@ class CalDAVSettingsTest extends TestCase {
 	/** @var IURLGenerator|MockObject */
 	private $urlGenerator;
 
+	/** @var IAppManager|MockObject */
+	private $appManager;
+
 	private CalDAVSettings $settings;
 
 	protected function setUp(): void {
@@ -32,7 +36,8 @@ class CalDAVSettingsTest extends TestCase {
 		$this->config = $this->createMock(IConfig::class);
 		$this->initialState = $this->createMock(IInitialState::class);
 		$this->urlGenerator = $this->createMock(IURLGenerator::class);
-		$this->settings = new CalDAVSettings($this->config, $this->initialState, $this->urlGenerator);
+		$this->appManager = $this->createMock(IAppManager::class);
+		$this->settings = new CalDAVSettings($this->config, $this->initialState, $this->urlGenerator, $this->appManager);
 	}
 
 	public function testGetForm(): void {
@@ -65,10 +70,23 @@ class CalDAVSettingsTest extends TestCase {
 	}
 
 	public function testGetSection(): void {
+		$this->appManager->expects(self::once())
+			->method('isBackendRequired')
+			->with(IAppManager::BACKEND_CALDAV)
+			->willReturn(true);
 		$this->assertEquals('groupware', $this->settings->getSection());
 	}
 
+	public function testGetSectionWithoutCaldavBackend(): void {
+		$this->appManager->expects(self::once())
+			->method('isBackendRequired')
+			->with(IAppManager::BACKEND_CALDAV)
+			->willReturn(false);
+		$this->assertEquals(null, $this->settings->getSection());
+	}
+
 	public function testGetPriority(): void {
 		$this->assertEquals(10, $this->settings->getPriority());
 	}
+
 }
diff --git a/lib/private/App/AppManager.php b/lib/private/App/AppManager.php
index 52a88a724ffb1b94fea9fa2cc2ecbb8b74d85e99..2f129ca77fa70b7c24e866888a6407fb652c094a 100644
--- a/lib/private/App/AppManager.php
+++ b/lib/private/App/AppManager.php
@@ -875,4 +875,16 @@ class AppManager implements IAppManager {
 
 		$this->config->setSystemValue('defaultapp', join(',', $defaultApps));
 	}
+
+	public function isBackendRequired(string $backend): bool {
+		foreach ($this->appInfos as $appInfo) {
+			foreach ($appInfo['dependencies']['backend'] as $appBackend) {
+				if ($backend === $appBackend) {
+					return true;
+				}
+			}
+		}
+
+		return false;
+	}
 }
diff --git a/lib/private/App/InfoParser.php b/lib/private/App/InfoParser.php
index d29b1d6596d999f45d40cdb92f34de348b21f11d..54afd0069fb1593a5164322485def760f05df82b 100644
--- a/lib/private/App/InfoParser.php
+++ b/lib/private/App/InfoParser.php
@@ -113,6 +113,12 @@ class InfoParser {
 		if (!array_key_exists('personal-section', $array['settings'])) {
 			$array['settings']['personal-section'] = [];
 		}
+		if (!array_key_exists('dependencies', $array)) {
+			$array['dependencies'] = [];
+		}
+		if (!array_key_exists('backend', $array['dependencies'])) {
+			$array['dependencies']['backend'] = [];
+		}
 
 		if (array_key_exists('types', $array)) {
 			if (is_array($array['types'])) {
@@ -177,10 +183,12 @@ class InfoParser {
 		if (isset($array['settings']['personal-section']) && !is_array($array['settings']['personal-section'])) {
 			$array['settings']['personal-section'] = [$array['settings']['personal-section']];
 		}
-
 		if (isset($array['navigations']['navigation']) && $this->isNavigationItem($array['navigations']['navigation'])) {
 			$array['navigations']['navigation'] = [$array['navigations']['navigation']];
 		}
+		if (isset($array['dependencies']['backend']) && !is_array($array['dependencies']['backend'])) {
+			$array['dependencies']['backend'] = [$array['dependencies']['backend']];
+		}
 
 		if ($this->cache !== null) {
 			$this->cache->set($fileCacheKey, json_encode($array));
diff --git a/lib/public/App/IAppManager.php b/lib/public/App/IAppManager.php
index 508143426f585dd30c8c928557d9065809b61716..e11bb0c36f5069b4e8c9461a9934a4e1a82f1551 100644
--- a/lib/public/App/IAppManager.php
+++ b/lib/public/App/IAppManager.php
@@ -19,6 +19,11 @@ use OCP\IUser;
  * @since 8.0.0
  */
 interface IAppManager {
+	/**
+	 * @since 30.0.0
+	 */
+	public const BACKEND_CALDAV = 'caldav';
+
 	/**
 	 * Returns the app information from "appinfo/info.xml".
 	 *
@@ -261,4 +266,14 @@ interface IAppManager {
 	 * @since 28.0.0
 	 */
 	public function setDefaultApps(array $defaultApps): void;
+
+	/**
+	 * Check whether the given backend is required by at least one app.
+	 *
+	 * @param self::BACKEND_* $backend Name of the backend, one of `self::BACKEND_*`
+	 * @return bool True if at least one app requires the backend
+	 *
+	 * @since 30.0.0
+	 */
+	public function isBackendRequired(string $backend): bool;
 }
diff --git a/resources/app-info.xsd b/resources/app-info.xsd
index a0b9439e350350a5c8464d52738098db27360bad..855d4962b4130764092287b3b108104de3bb507f 100644
--- a/resources/app-info.xsd
+++ b/resources/app-info.xsd
@@ -563,6 +563,8 @@
                         maxOccurs="1"/>
             <xs:element name="architecture" type="architecture" minOccurs="0"
                         maxOccurs="unbounded"/>
+            <xs:element name="backend" type="backend" minOccurs="0"
+                        maxOccurs="unbounded"/>
         </xs:sequence>
     </xs:complexType>
 
@@ -633,6 +635,12 @@
         </xs:restriction>
     </xs:simpleType>
 
+    <xs:simpleType name="backend">
+        <xs:restriction base="xs:string">
+            <xs:enumeration value="caldav"/>
+        </xs:restriction>
+    </xs:simpleType>
+
     <xs:complexType name="repair-steps">
         <xs:sequence>
             <xs:element name="pre-migration" type="steps" minOccurs="0"
diff --git a/tests/data/app/expected-info.json b/tests/data/app/expected-info.json
index 8527f18a2c0541e46e4e3f7c7ca9ff2104c4ad77..c3ea27af7d398f283c59d8814b6315f63dfb7e53 100644
--- a/tests/data/app/expected-info.json
+++ b/tests/data/app/expected-info.json
@@ -65,7 +65,10 @@
 				"min-version": "7.0.1",
 				"max-version": "8"
 			}
-		}
+		},
+		"backend": [
+			"caldav"
+		]
 	},
 	"repair-steps": {
 		"install": [],
diff --git a/tests/data/app/navigation-one-item.json b/tests/data/app/navigation-one-item.json
index c9002da6b0dbae3dbe20a3b2382c12a32a97b804..2bd814615868821931aed9ac92d2c3f0f2d8a703 100644
--- a/tests/data/app/navigation-one-item.json
+++ b/tests/data/app/navigation-one-item.json
@@ -29,7 +29,8 @@
         "min-version": "16",
         "max-version": "16"
       }
-    }
+    },
+    "backend": []
   },
   "background-jobs": [
     "OCA\\Activity\\BackgroundJob\\EmailNotification",
@@ -82,4 +83,4 @@
     "uninstall": []
   },
   "two-factor-providers": []
-}
\ No newline at end of file
+}
diff --git a/tests/data/app/navigation-two-items.json b/tests/data/app/navigation-two-items.json
index a7579217238fd3e378acd89856c7b8ce10a62c87..4b081d3bbd929925f588a16ccf5a247ac646972a 100644
--- a/tests/data/app/navigation-two-items.json
+++ b/tests/data/app/navigation-two-items.json
@@ -29,7 +29,8 @@
         "min-version": "16",
         "max-version": "16"
       }
-    }
+    },
+    "backend": []
   },
   "background-jobs": [
     "OCA\\Activity\\BackgroundJob\\EmailNotification",
@@ -88,4 +89,4 @@
     "uninstall": []
   },
   "two-factor-providers": []
-}
\ No newline at end of file
+}
diff --git a/tests/data/app/valid-info.xml b/tests/data/app/valid-info.xml
index 9044c00f3536e35cef6cae0f3b8477037eadffd4..d25697883999115ae41f436cb41cd194dbb86e39 100644
--- a/tests/data/app/valid-info.xml
+++ b/tests/data/app/valid-info.xml
@@ -34,5 +34,6 @@
 		<lib>curl</lib>
 		<os>Linux</os>
 		<owncloud min-version="7.0.1" max-version="8" />
+		<backend>caldav</backend>
 	</dependencies>
 </info>
diff --git a/tests/lib/App/AppManagerTest.php b/tests/lib/App/AppManagerTest.php
index c38cadf8e3817c9b35e524fdfff2fe4f3534b4e2..03c9d474c55b6544722174960c1230bbe2d1b485 100644
--- a/tests/lib/App/AppManagerTest.php
+++ b/tests/lib/App/AppManagerTest.php
@@ -885,4 +885,52 @@ class AppManagerTest extends TestCase {
 
 		$this->assertEquals($expectedApp, $this->manager->getDefaultAppForUser(null, $withFallbacks));
 	}
+
+	public static function isBackendRequiredDataProvider(): array {
+		return [
+			// backend available
+			[
+				'caldav',
+				['app1' => ['caldav']],
+				true,
+			],
+			[
+				'caldav',
+				['app1' => [], 'app2' => ['foo'], 'app3' => ['caldav']],
+				true,
+			],
+			// backend not available
+			[
+				'caldav',
+				['app3' => [], 'app1' => ['foo'], 'app2' => ['bar', 'baz']],
+				false,
+			],
+			// no app available
+			[
+				'caldav',
+				[],
+				false,
+			],
+		];
+	}
+
+	/**
+	 * @dataProvider isBackendRequiredDataProvider
+	 */
+	public function testIsBackendRequired(
+		string $backend,
+		array $appBackends,
+		bool $expected,
+	): void {
+		$appInfoData = array_map(
+			static fn (array $backends) => ['dependencies' => ['backend' => $backends]],
+			$appBackends,
+		);
+
+		$reflection = new \ReflectionClass($this->manager);
+		$property = $reflection->getProperty('appInfos');
+		$property->setValue($this->manager, $appInfoData);
+
+		$this->assertEquals($expected, $this->manager->isBackendRequired($backend));
+	}
 }