diff --git a/api/funkwhale_api/music/views.py b/api/funkwhale_api/music/views.py
index 90b833a2b5aa257bd8d0ad4bbabbd234286feaf0..34ec02632cf44069c52b28a81ccf83e96feca148 100644
--- a/api/funkwhale_api/music/views.py
+++ b/api/funkwhale_api/music/views.py
@@ -4,7 +4,6 @@ import urllib
 from django.conf import settings
 from django.db import transaction
 from django.db.models import Count, Prefetch, Sum, F, Q
-from django.db.models.functions import Length
 from django.utils import timezone
 
 from rest_framework import mixins
@@ -24,6 +23,7 @@ from funkwhale_api.federation import api_serializers as federation_api_serialize
 from funkwhale_api.federation import decorators as federation_decorators
 from funkwhale_api.federation import routes
 from funkwhale_api.tags.models import Tag, TaggedItem
+from funkwhale_api.tags.serializers import TagSerializer
 from funkwhale_api.users.oauth import permissions as oauth_permissions
 
 from . import filters, licenses, models, serializers, tasks, utils
@@ -339,7 +339,7 @@ def handle_serve(upload, user, format=None, max_bitrate=None, proxy_media=True):
         f = transcoded_version
         file_path = get_file_path(f.audio_file)
         mt = f.mimetype
-    if not proxy_media:
+    if not proxy_media and f.audio_file:
         # we simply issue a 302 redirect to the real URL
         response = Response(status=302)
         response["Location"] = f.audio_file.url
@@ -482,6 +482,7 @@ class Search(views.APIView):
             "albums": serializers.AlbumSerializer(
                 self.get_albums(query), many=True
             ).data,
+            "tags": TagSerializer(self.get_tags(query), many=True).data,
         }
         return Response(results, status=200)
 
@@ -520,15 +521,8 @@ class Search(views.APIView):
     def get_tags(self, query):
         search_fields = ["name__unaccent"]
         query_obj = utils.get_query(query, search_fields)
-
-        # We want the shortest tag first
-        qs = (
-            Tag.objects.all()
-            .annotate(name_length=Length("name"))
-            .order_by("name_length")
-        )
-
-        return qs.filter(query_obj)[: self.max_results]
+        qs = Tag.objects.all().filter(query_obj)
+        return common_utils.order_for_search(qs, "name")[: self.max_results]
 
 
 class LicenseViewSet(viewsets.ReadOnlyModelViewSet):
diff --git a/api/funkwhale_api/radios/filters.py b/api/funkwhale_api/radios/filters.py
index 810673bd664f6b7ddf56aee7403a2dfac5445b83..a92dbae889dc7b08d679147b75f92f5074189b74 100644
--- a/api/funkwhale_api/radios/filters.py
+++ b/api/funkwhale_api/radios/filters.py
@@ -178,9 +178,9 @@ class TagFilter(RadioFilter):
             "autocomplete_fields": {
                 "remoteValues": "results",
                 "name": "name",
-                "value": "slug",
+                "value": "name",
             },
-            "autocomplete_qs": "query={query}",
+            "autocomplete_qs": "q={query}&ordering=length",
             "label": "Tags",
             "placeholder": "Select tags",
         }
@@ -189,4 +189,8 @@ class TagFilter(RadioFilter):
     label = "Tag"
 
     def get_query(self, candidates, names, **kwargs):
-        return Q(tags__slug__in=names)
+        return (
+            Q(tagged_items__tag__name__in=names)
+            | Q(artist__tagged_items__tag__name__in=names)
+            | Q(album__tagged_items__tag__name__in=names)
+        )
diff --git a/api/funkwhale_api/radios/radios.py b/api/funkwhale_api/radios/radios.py
index 8940cdc1525e371a358b3bb5f2f6fcc827429962..86c84ad137b98b16d8b2e4ce50d5679e58131afd 100644
--- a/api/funkwhale_api/radios/radios.py
+++ b/api/funkwhale_api/radios/radios.py
@@ -2,6 +2,7 @@ import random
 
 from django.core.exceptions import ValidationError
 from django.db import connection
+from django.db.models import Q
 from rest_framework import serializers
 
 from funkwhale_api.moderation import filters as moderation_filters
@@ -14,6 +15,8 @@ from .registries import registry
 
 
 class SimpleRadio(object):
+    related_object_field = None
+
     def clean(self, instance):
         return
 
@@ -146,6 +149,8 @@ class CustomRadio(SessionRadio):
 class RelatedObjectRadio(SessionRadio):
     """Abstract radio related to an object (tag, artist, user...)"""
 
+    related_object_field = serializers.IntegerField(required=True)
+
     def clean(self, instance):
         super().clean(instance)
         if not instance.related_object:
@@ -162,10 +167,22 @@ class RelatedObjectRadio(SessionRadio):
 @registry.register(name="tag")
 class TagRadio(RelatedObjectRadio):
     model = Tag
+    related_object_field = serializers.CharField(required=True)
+
+    def get_related_object(self, name):
+        return self.model.objects.get(name=name)
 
     def get_queryset(self, **kwargs):
         qs = super().get_queryset(**kwargs)
-        return qs.filter(tagged_items__tag=self.session.related_object)
+        query = (
+            Q(tagged_items__tag=self.session.related_object)
+            | Q(artist__tagged_items__tag=self.session.related_object)
+            | Q(album__tagged_items__tag=self.session.related_object)
+        )
+        return qs.filter(query)
+
+    def get_related_object_id_repr(self, obj):
+        return obj.name
 
 
 def weighted_choice(choices):
diff --git a/api/funkwhale_api/radios/serializers.py b/api/funkwhale_api/radios/serializers.py
index 397452ecc0e5c6646a82ea663fdc6bb099e2f803..65e48449a8e704a42e118646187da1ae24b117a1 100644
--- a/api/funkwhale_api/radios/serializers.py
+++ b/api/funkwhale_api/radios/serializers.py
@@ -54,6 +54,9 @@ class RadioSessionTrackSerializer(serializers.ModelSerializer):
 
 
 class RadioSessionSerializer(serializers.ModelSerializer):
+
+    related_object_id = serializers.CharField(required=False, allow_null=True)
+
     class Meta:
         model = models.RadioSession
         fields = (
@@ -66,7 +69,17 @@ class RadioSessionSerializer(serializers.ModelSerializer):
         )
 
     def validate(self, data):
-        registry[data["radio_type"]]().validate_session(data, **self.context)
+        radio_conf = registry[data["radio_type"]]()
+        if radio_conf.related_object_field:
+            try:
+                data[
+                    "related_object_id"
+                ] = radio_conf.related_object_field.to_internal_value(
+                    data["related_object_id"]
+                )
+            except KeyError:
+                raise serializers.ValidationError("Radio requires a related object")
+        radio_conf.validate_session(data, **self.context)
         return data
 
     def create(self, validated_data):
@@ -77,3 +90,11 @@ class RadioSessionSerializer(serializers.ModelSerializer):
                 validated_data["related_object_id"]
             )
         return super().create(validated_data)
+
+    def to_representation(self, instance):
+        repr = super().to_representation(instance)
+        radio_conf = registry[repr["radio_type"]]()
+        handler = getattr(radio_conf, "get_related_object_id_repr", None)
+        if handler and instance.related_object:
+            repr["related_object_id"] = handler(instance.related_object)
+        return repr
diff --git a/api/tests/radios/test_radios.py b/api/tests/radios/test_radios.py
index 36f76e1c1f551db62ab658608a298b35bf11eddb..040217aac39308d5f6d77f4236e33fa05381b8a1 100644
--- a/api/tests/radios/test_radios.py
+++ b/api/tests/radios/test_radios.py
@@ -197,16 +197,19 @@ def test_can_start_artist_radio(factories):
 
 def test_can_start_tag_radio(factories):
     user = factories["users.User"]()
-    factories["music.Upload"].create_batch(5)
     tag = factories["tags.Tag"]()
-    good_files = factories["music.Upload"].create_batch(5, track__set_tags=[tag])
-    good_tracks = [f.track for f in good_files]
+    good_tracks = [
+        factories["music.Track"](set_tags=[tag.name]),
+        factories["music.Track"](album__set_tags=[tag.name]),
+        factories["music.Track"](album__artist__set_tags=[tag.name]),
+    ]
+    factories["music.Track"].create_batch(3, set_tags=["notrock"])
 
     radio = radios.TagRadio()
     session = radio.start_session(user, related_object=tag)
     assert session.radio_type == "tag"
 
-    for i in range(5):
+    for i in range(3):
         assert radio.pick(filter_playable=False) in good_tracks
 
 
diff --git a/api/tests/radios/test_serializers.py b/api/tests/radios/test_serializers.py
new file mode 100644
index 0000000000000000000000000000000000000000..54019d5e12559b26419f0564c1651bff259041a0
--- /dev/null
+++ b/api/tests/radios/test_serializers.py
@@ -0,0 +1,44 @@
+from funkwhale_api.radios import serializers
+
+
+def test_create_tag_radio(factories):
+    tag = factories["tags.Tag"]()
+
+    data = {"radio_type": "tag", "related_object_id": tag.name}
+
+    serializer = serializers.RadioSessionSerializer(data=data)
+    assert serializer.is_valid(raise_exception=True) is True
+
+    session = serializer.save()
+
+    assert session.related_object_id == tag.pk
+    assert session.related_object == tag
+
+
+def test_create_artist_radio(factories):
+    artist = factories["music.Artist"]()
+
+    data = {"radio_type": "artist", "related_object_id": artist.pk}
+
+    serializer = serializers.RadioSessionSerializer(data=data)
+    assert serializer.is_valid(raise_exception=True) is True
+
+    session = serializer.save()
+
+    assert session.related_object_id == artist.pk
+    assert session.related_object == artist
+
+
+def test_tag_radio_repr(factories):
+    tag = factories["tags.Tag"]()
+    session = factories["radios.RadioSession"](related_object=tag, radio_type="tag")
+
+    expected = {
+        "id": session.pk,
+        "radio_type": "tag",
+        "custom_radio": None,
+        "user": session.user.pk,
+        "related_object_id": tag.name,
+        "creation_date": session.creation_date.isoformat().split("+")[0] + "Z",
+    }
+    assert serializers.RadioSessionSerializer(session).data == expected
diff --git a/front/src/components/audio/SearchBar.vue b/front/src/components/audio/SearchBar.vue
index e0c343fede1fb99263b18b3427acc6bd3b7e8f27..d7445d13b09bf409125cb16956df137fe4f19f2d 100644
--- a/front/src/components/audio/SearchBar.vue
+++ b/front/src/components/audio/SearchBar.vue
@@ -32,6 +32,7 @@ export default {
     let artistLabel = this.$pgettext('*/*/*/Noun', 'Artist')
     let albumLabel = this.$pgettext('*/*/*', 'Album')
     let trackLabel = this.$pgettext('*/*/*/Noun', 'Track')
+    let tagLabel = this.$pgettext('*/*/*/Noun', 'Tag')
     let self = this
     var searchQuery;
 
@@ -75,6 +76,9 @@ export default {
               },
               getDescription (r) {
                 return ''
+              },
+              getId (t) {
+                return t.id
               }
             },
             {
@@ -86,6 +90,9 @@ export default {
               },
               getDescription (r) {
                 return ''
+              },
+              getId (t) {
+                return t.id
               }
             },
             {
@@ -97,6 +104,23 @@ export default {
               },
               getDescription (r) {
                 return ''
+              },
+              getId (t) {
+                return t.id
+              }
+            },
+            {
+              code: 'tags',
+              route: 'library.tags.detail',
+              name: tagLabel,
+              getTitle (r) {
+                return r.name
+              },
+              getDescription (r) {
+                return ''
+              },
+              getId (t) {
+                return t.name
               }
             }
           ]
@@ -106,13 +130,14 @@ export default {
               results: []
             }
             initialResponse[category.code].forEach(result => {
+              let id = category.getId(result)
               results[category.code].results.push({
                 title: category.getTitle(result),
-                id: result.id,
+                id,
                 routerUrl: {
                   name: category.route,
                   params: {
-                    id: result.id
+                    id
                   }
                 },
                 description: category.getDescription(result)
diff --git a/front/src/components/library/TagDetail.vue b/front/src/components/library/TagDetail.vue
index 86ad1562a342397fbb9e2fdadbc20647318ce79d..904bce7aecb8b21aa0a8f513c8cce20367e4cfce 100644
--- a/front/src/components/library/TagDetail.vue
+++ b/front/src/components/library/TagDetail.vue
@@ -6,6 +6,7 @@
           {{ labels.title }}
         </span>
       </h2>
+      <radio-button type="tag" :object-id="id"></radio-button>
       <div class="ui hidden divider"></div>
       <div class="ui row">
         <artist-widget :controls="false" :filters="{playable: true, ordering: '-creation_date', tag: id}">
@@ -39,10 +40,10 @@
 
 <script>
 
-
 import TrackWidget from "@/components/audio/track/Widget"
 import AlbumWidget from "@/components/audio/album/Widget"
 import ArtistWidget from "@/components/audio/artist/Widget"
+import RadioButton from "@/components/radios/Button"
 
 export default {
   props: {
@@ -52,6 +53,7 @@ export default {
     ArtistWidget,
     AlbumWidget,
     TrackWidget,
+    RadioButton,
   },
   computed: {
     labels() {
diff --git a/front/src/components/radios/Button.vue b/front/src/components/radios/Button.vue
index e66f97ad5fafe1622e6a586c8e2d88155e87a766..f335a6596273838e844f1357f7c9c5503ace7ff7 100644
--- a/front/src/components/radios/Button.vue
+++ b/front/src/components/radios/Button.vue
@@ -12,7 +12,7 @@ export default {
   props: {
     customRadioId: {required: false},
     type: {type: String, required: false},
-    objectId: {type: Number, default: null}
+    objectId: {default: null}
   },
   methods: {
     toggleRadio () {