diff --git a/api/funkwhale_api/subsonic/renderers.py b/api/funkwhale_api/subsonic/renderers.py
index e4b6470511378bbff21d824896a4210dd84c6c43..527b3fa1e2e9274c8041cbbd6c74e8741a55d261 100644
--- a/api/funkwhale_api/subsonic/renderers.py
+++ b/api/funkwhale_api/subsonic/renderers.py
@@ -53,5 +53,8 @@ def dict_to_xml_tree(root_tag, d, parent=None):
             for obj in value:
                 root.append(dict_to_xml_tree(key, obj, parent=root))
         else:
-            root.set(key, str(value))
+            if key == "value":
+                root.text = str(value)
+            else:
+                root.set(key, str(value))
     return root
diff --git a/api/funkwhale_api/subsonic/serializers.py b/api/funkwhale_api/subsonic/serializers.py
index a53ad464038316f5efbd5810bd8a995e33a744ec..994ad682deb4d73251ef72659b4ffd2c0f9df22f 100644
--- a/api/funkwhale_api/subsonic/serializers.py
+++ b/api/funkwhale_api/subsonic/serializers.py
@@ -263,3 +263,11 @@ class ScrobbleSerializer(serializers.Serializer):
         return history_models.Listening.objects.create(
             user=self.context["user"], track=data["id"]
         )
+
+
+def get_genre_data(tag):
+    return {
+        "songCount": getattr(tag, "_tracks_count", 0),
+        "albumCount": getattr(tag, "_albums_count", 0),
+        "value": tag.name,
+    }
diff --git a/api/funkwhale_api/subsonic/views.py b/api/funkwhale_api/subsonic/views.py
index 909f7a6e4853c07e318ec7c9ddd15e821147a52a..e206555abdd74eee9b92605fc61f5d207b5cc1f3 100644
--- a/api/funkwhale_api/subsonic/views.py
+++ b/api/funkwhale_api/subsonic/views.py
@@ -2,6 +2,8 @@ import datetime
 import functools
 
 from django.conf import settings
+from django.contrib.contenttypes.models import ContentType
+from django.db.models import Count, Q
 from django.utils import timezone
 from rest_framework import exceptions
 from rest_framework import permissions as rest_permissions
@@ -18,6 +20,7 @@ from funkwhale_api.music import models as music_models
 from funkwhale_api.music import utils
 from funkwhale_api.music import views as music_views
 from funkwhale_api.playlists import models as playlists_models
+from funkwhale_api.tags import models as tags_models
 from funkwhale_api.users import models as users_models
 
 from . import authentication, filters, negotiation, serializers
@@ -362,6 +365,26 @@ class SubsonicViewSet(viewsets.GenericViewSet):
         queryset = filterset.qs
         actor = utils.get_actor_from_request(request)
         queryset = queryset.playable_by(actor)
+        type = data.get("type", "alphabeticalByArtist")
+
+        if type == "alphabeticalByArtist":
+            queryset = queryset.order_by("artist__name")
+        elif type == "random":
+            queryset = queryset.order_by("?")
+        elif type == "alphabeticalByName" or not type:
+            queryset = queryset.order_by("artist__title")
+        elif type == "recent" or not type:
+            queryset = queryset.exclude(release_date__in=["", None]).order_by(
+                "-release_date"
+            )
+        elif type == "newest" or not type:
+            queryset = queryset.order_by("-creation_date")
+        elif type == "byGenre" and data.get("genre"):
+            genre = data.get("genre")
+            queryset = queryset.filter(
+                Q(tagged_items__tag__name=genre)
+                | Q(artist__tagged_items__tag__name=genre)
+            )
 
         try:
             offset = int(data["offset"])
@@ -669,3 +692,29 @@ class SubsonicViewSet(viewsets.GenericViewSet):
             listening = serializer.save()
             record.send(listening)
         return response.Response({})
+
+    @action(
+        detail=False,
+        methods=["get", "post"],
+        url_name="get_genres",
+        url_path="getGenres",
+    )
+    def get_genres(self, request, *args, **kwargs):
+        album_ct = ContentType.objects.get_for_model(music_models.Album)
+        track_ct = ContentType.objects.get_for_model(music_models.Track)
+        queryset = (
+            tags_models.Tag.objects.annotate(
+                _albums_count=Count(
+                    "tagged_items", filter=Q(tagged_items__content_type=album_ct)
+                ),
+                _tracks_count=Count(
+                    "tagged_items", filter=Q(tagged_items__content_type=track_ct)
+                ),
+            )
+            .exclude(_tracks_count=0, _albums_count=0)
+            .order_by("name")
+        )
+        data = {
+            "genres": {"genre": [serializers.get_genre_data(tag) for tag in queryset]}
+        }
+        return response.Response(data)
diff --git a/api/funkwhale_api/tags/models.py b/api/funkwhale_api/tags/models.py
index a5f3a37359888232c72124b5e0ce3ea55981cb75..1416a62bc02d256a56fd9b16291b470e89242850 100644
--- a/api/funkwhale_api/tags/models.py
+++ b/api/funkwhale_api/tags/models.py
@@ -33,14 +33,12 @@ class TaggedItemQuerySet(models.QuerySet):
 
 class TaggedItem(models.Model):
     creation_date = models.DateTimeField(default=timezone.now)
-    tag = models.ForeignKey(
-        Tag, related_name="%(app_label)s_%(class)s_items", on_delete=models.CASCADE
-    )
+    tag = models.ForeignKey(Tag, related_name="tagged_items", on_delete=models.CASCADE)
     content_type = models.ForeignKey(
         ContentType,
         on_delete=models.CASCADE,
         verbose_name=_("Content type"),
-        related_name="%(app_label)s_%(class)s_tagged_items",
+        related_name="tagged_items",
     )
     object_id = models.IntegerField(verbose_name=_("Object id"), db_index=True)
     content_object = GenericForeignKey()
diff --git a/api/tests/subsonic/test_renderers.py b/api/tests/subsonic/test_renderers.py
index acd5500e665b6d4f9d32c0ea6cd9689a9bb37421..501ae48ce559277baf5b378b41c042d7241ea9a3 100644
--- a/api/tests/subsonic/test_renderers.py
+++ b/api/tests/subsonic/test_renderers.py
@@ -67,9 +67,12 @@ def test_json_renderer():
 
 
 def test_xml_renderer_dict_to_xml():
-    payload = {"hello": "world", "item": [{"this": 1}, {"some": "node"}]}
+    payload = {
+        "hello": "world",
+        "item": [{"this": 1, "value": "text"}, {"some": "node"}],
+    }
     expected = """<?xml version="1.0" encoding="UTF-8"?>
-<key hello="world"><item this="1" /><item some="node" /></key>"""
+<key hello="world"><item this="1">text</item><item some="node" /></key>"""
     result = renderers.dict_to_xml_tree("key", payload)
     exp = ET.fromstring(expected)
     assert ET.tostring(result) == ET.tostring(exp)
diff --git a/api/tests/subsonic/test_views.py b/api/tests/subsonic/test_views.py
index e227f4d1d1abbb9db924cc20b6065ea37f58c9dd..361a46a73bec60a91e1b042b6b12ff98cb076c28 100644
--- a/api/tests/subsonic/test_views.py
+++ b/api/tests/subsonic/test_views.py
@@ -375,6 +375,28 @@ def test_get_random_songs(f, db, logged_in_api_client, factories, mocker):
     order_by.assert_called_once_with("?")
 
 
+@pytest.mark.parametrize("f", ["json"])
+def test_get_genres(f, db, logged_in_api_client, factories, mocker):
+    url = reverse("api:subsonic-get_genres")
+    assert url.endswith("getGenres") is True
+    tag1 = factories["tags.Tag"](name="Pop")
+    tag2 = factories["tags.Tag"](name="Rock")
+
+    factories["music.Album"](set_tags=[tag1.name, tag2.name])
+    factories["music.Track"](set_tags=[tag1.name])
+    response = logged_in_api_client.get(url)
+
+    assert response.status_code == 200
+    assert response.data == {
+        "genres": {
+            "genre": [
+                {"songCount": 1, "albumCount": 1, "value": tag1.name},
+                {"songCount": 0, "albumCount": 1, "value": tag2.name},
+            ]
+        }
+    }
+
+
 @pytest.mark.parametrize("f", ["json"])
 def test_get_starred(f, db, logged_in_api_client, factories):
     url = reverse("api:subsonic-get_starred")
@@ -426,6 +448,27 @@ def test_get_album_list2_pagination(f, db, logged_in_api_client, factories):
     }
 
 
+@pytest.mark.parametrize("f", ["json"])
+def test_get_album_list2_by_genre(f, db, logged_in_api_client, factories):
+    url = reverse("api:subsonic-get_album_list2")
+    assert url.endswith("getAlbumList2") is True
+    album1 = factories["music.Album"](
+        artist__name="Artist1", playable=True, set_tags=["Rock"]
+    )
+    album2 = factories["music.Album"](
+        artist__name="Artist2", playable=True, artist__set_tags=["Rock"]
+    )
+    factories["music.Album"](playable=True, set_tags=["Pop"])
+    response = logged_in_api_client.get(
+        url, {"f": f, "type": "byGenre", "size": 5, "offset": 0, "genre": "rock"}
+    )
+
+    assert response.status_code == 200
+    assert response.data == {
+        "albumList2": {"album": serializers.get_album_list2_data([album1, album2])}
+    }
+
+
 @pytest.mark.parametrize("f", ["json"])
 def test_search3(f, db, logged_in_api_client, factories):
     url = reverse("api:subsonic-search3")