diff --git a/changelog.d/13035.feature b/changelog.d/13035.feature
new file mode 100644
index 0000000000..cfca3ab4b7
--- /dev/null
+++ b/changelog.d/13035.feature
@@ -0,0 +1 @@
+Allow server admins to customise the response of the `/.well-known/matrix/client` endpoint.
diff --git a/docs/usage/configuration/config_documentation.md b/docs/usage/configuration/config_documentation.md
index 7c9860c3e1..58a74ace48 100644
--- a/docs/usage/configuration/config_documentation.md
+++ b/docs/usage/configuration/config_documentation.md
@@ -230,6 +230,22 @@ Example configuration:
serve_server_wellknown: true
```
---
+### `extra_well_known_client_content `
+
+This option allows server runners to add arbitrary key-value pairs to the [client-facing `.well-known` response](https://spec.matrix.org/latest/client-server-api/#well-known-uri).
+Note that the `public_baseurl` config option must be provided for Synapse to serve a response to `/.well-known/matrix/client` at all.
+
+If this option is provided, it parses the given yaml to json and
+serves it on `/.well-known/matrix/client` endpoint
+alongside the standard properties.
+
+Example configuration:
+```yaml
+extra_well_known_client_content :
+ option1: value1
+ option2: value2
+```
+---
### `soft_file_limit`
Set the soft limit on the number of file descriptors synapse can use.
@@ -3580,3 +3596,4 @@ background_updates:
min_batch_size: 10
default_batch_size: 50
```
+
diff --git a/synapse/config/server.py b/synapse/config/server.py
index 828938e5ec..085fe22c51 100644
--- a/synapse/config/server.py
+++ b/synapse/config/server.py
@@ -301,6 +301,26 @@ class ServerConfig(Config):
"public_baseurl cannot contain query parameters or a #-fragment"
)
+ self.extra_well_known_client_content = config.get(
+ "extra_well_known_client_content", {}
+ )
+
+ if not isinstance(self.extra_well_known_client_content, dict):
+ raise ConfigError(
+ "extra_well_known_content must be a dictionary of key-value pairs"
+ )
+
+ if "m.homeserver" in self.extra_well_known_client_content:
+ raise ConfigError(
+ "m.homeserver is not supported in extra_well_known_content, "
+ "use public_baseurl in base config instead."
+ )
+ if "m.identity_server" in self.extra_well_known_client_content:
+ raise ConfigError(
+ "m.identity_server is not supported in extra_well_known_content, "
+ "use default_identity_server in base config instead."
+ )
+
# Whether to enable user presence.
presence_config = config.get("presence") or {}
self.use_presence = presence_config.get("enabled")
diff --git a/synapse/rest/well_known.py b/synapse/rest/well_known.py
index 04b035a1b1..6f7ac54c65 100644
--- a/synapse/rest/well_known.py
+++ b/synapse/rest/well_known.py
@@ -11,7 +11,6 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
-
import logging
from typing import TYPE_CHECKING, Optional
@@ -44,6 +43,14 @@ class WellKnownBuilder:
"base_url": self._config.registration.default_identity_server
}
+ if self._config.server.extra_well_known_client_content:
+ for (
+ key,
+ value,
+ ) in self._config.server.extra_well_known_client_content.items():
+ if key not in result:
+ result[key] = value
+
return result
diff --git a/tests/rest/test_well_known.py b/tests/rest/test_well_known.py
index 11f78f52b8..d8faafec75 100644
--- a/tests/rest/test_well_known.py
+++ b/tests/rest/test_well_known.py
@@ -59,6 +59,28 @@ class WellKnownTests(unittest.HomeserverTestCase):
self.assertEqual(channel.code, HTTPStatus.NOT_FOUND)
+ @unittest.override_config(
+ {
+ "public_baseurl": "https://tesths",
+ "default_identity_server": "https://testis",
+ "extra_well_known_client_content": {"custom": False},
+ }
+ )
+ def test_client_well_known_custom(self) -> None:
+ channel = self.make_request(
+ "GET", "/.well-known/matrix/client", shorthand=False
+ )
+
+ self.assertEqual(channel.code, HTTPStatus.OK)
+ self.assertEqual(
+ channel.json_body,
+ {
+ "m.homeserver": {"base_url": "https://tesths/"},
+ "m.identity_server": {"base_url": "https://testis"},
+ "custom": False,
+ },
+ )
+
@unittest.override_config({"serve_server_wellknown": True})
def test_server_well_known(self) -> None:
channel = self.make_request(
|