diff --git a/api/funkwhale_api/common/factories.py b/api/funkwhale_api/common/factories.py
index d6a0636039f00fc1dfff8af764b62ded83939c0d..9af602de7df70ce22f663161a59cd3e1d56aed22 100644
--- a/api/funkwhale_api/common/factories.py
+++ b/api/funkwhale_api/common/factories.py
@@ -26,3 +26,12 @@ class AttachmentFactory(NoUpdateOnCreate, factory.django.DjangoModelFactory):
 
     class Meta:
         model = "common.Attachment"
+
+
+@registry.register
+class CommonFactory(NoUpdateOnCreate, factory.django.DjangoModelFactory):
+    text = factory.Faker("paragraph")
+    content_type = "text/plain"
+
+    class Meta:
+        model = "common.Content"
diff --git a/api/funkwhale_api/common/migrations/0006_content.py b/api/funkwhale_api/common/migrations/0006_content.py
new file mode 100644
index 0000000000000000000000000000000000000000..9cab5e765e93b76faee207aef1d2d9d8c8d3b8cc
--- /dev/null
+++ b/api/funkwhale_api/common/migrations/0006_content.py
@@ -0,0 +1,21 @@
+# Generated by Django 2.2.7 on 2020-01-13 10:14
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('common', '0005_auto_20191125_1421'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='Content',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('text', models.CharField(blank=True, max_length=5000, null=True)),
+                ('content_type', models.CharField(max_length=100)),
+            ],
+        ),
+    ]
diff --git a/api/funkwhale_api/common/models.py b/api/funkwhale_api/common/models.py
index 4e4fc14dd55ce87ca72df1cd5d1c8d26faaea2cc..8750d5d7a129de2301a5622a608bab485962b748 100644
--- a/api/funkwhale_api/common/models.py
+++ b/api/funkwhale_api/common/models.py
@@ -24,6 +24,14 @@ from . import utils
 from . import validators
 
 
+CONTENT_TEXT_MAX_LENGTH = 5000
+CONTENT_TEXT_SUPPORTED_TYPES = [
+    "text/html",
+    "text/markdown",
+    "text/plain",
+]
+
+
 @Field.register_lookup
 class NotEqual(Lookup):
     lookup_name = "ne"
@@ -273,6 +281,15 @@ class MutationAttachment(models.Model):
         unique_together = ("attachment", "mutation")
 
 
+class Content(models.Model):
+    """
+    A text content that can be associated to other models, like a description, a summary, etc.
+    """
+
+    text = models.CharField(max_length=CONTENT_TEXT_MAX_LENGTH, blank=True, null=True)
+    content_type = models.CharField(max_length=100)
+
+
 @receiver(models.signals.post_save, sender=Attachment)
 def warm_attachment_thumbnails(sender, instance, **kwargs):
     if not instance.file or not settings.CREATE_IMAGE_THUMBNAILS:
@@ -302,3 +319,18 @@ def trigger_mutation_post_init(sender, instance, created, **kwargs):
     except AttributeError:
         return
     handler(instance)
+
+
+CONTENT_FKS = {
+    "music.Track": ["description"],
+    "music.Album": ["description"],
+    "music.Artist": ["description"],
+}
+
+
+@receiver(models.signals.post_delete, sender=None)
+def remove_attached_content(sender, instance, **kwargs):
+    fk_fields = CONTENT_FKS.get(instance._meta.label, [])
+    for field in fk_fields:
+        if getattr(instance, "{}_id".format(field)):
+            getattr(instance, field).delete()
diff --git a/api/funkwhale_api/common/mutations.py b/api/funkwhale_api/common/mutations.py
index 13a5a97de03619f90b81e26b6ce8d6dabd502963..586e86ec17c0865c48d91b88843d5fe0ac0d02bd 100644
--- a/api/funkwhale_api/common/mutations.py
+++ b/api/funkwhale_api/common/mutations.py
@@ -86,7 +86,6 @@ class MutationSerializer(serializers.Serializer):
 
 class UpdateMutationSerializer(serializers.ModelSerializer, MutationSerializer):
     serialized_relations = {}
-    previous_state_handlers = {}
 
     def __init__(self, *args, **kwargs):
         # we force partial mode, because update mutations are partial
@@ -141,9 +140,12 @@ class UpdateMutationSerializer(serializers.ModelSerializer, MutationSerializer):
             obj,
             *list(validated_data.keys()),
             serialized_relations=self.serialized_relations,
-            handlers=self.previous_state_handlers,
+            handlers=self.get_previous_state_handlers(),
         )
 
+    def get_previous_state_handlers(self):
+        return {}
+
 
 def get_update_previous_state(obj, *fields, serialized_relations={}, handlers={}):
     if not fields:
diff --git a/api/funkwhale_api/common/serializers.py b/api/funkwhale_api/common/serializers.py
index fa889f9e887be3511675c521c31789c9d9bc559a..38be2b5bc14551d022a10469399df647b4cc92bd 100644
--- a/api/funkwhale_api/common/serializers.py
+++ b/api/funkwhale_api/common/serializers.py
@@ -11,6 +11,7 @@ from django.utils.encoding import smart_text
 from django.utils.translation import ugettext_lazy as _
 
 from . import models
+from . import utils
 
 
 class RelatedField(serializers.RelatedField):
@@ -308,3 +309,12 @@ class AttachmentSerializer(serializers.Serializer):
         return models.Attachment.objects.create(
             file=validated_data["file"], actor=validated_data["actor"]
         )
+
+
+class ContentSerializer(serializers.Serializer):
+    text = serializers.CharField(max_length=models.CONTENT_TEXT_MAX_LENGTH)
+    content_type = serializers.ChoiceField(choices=models.CONTENT_TEXT_SUPPORTED_TYPES,)
+    html = serializers.SerializerMethodField()
+
+    def get_html(self, o):
+        return utils.render_html(o.text, o.content_type)
diff --git a/api/funkwhale_api/common/utils.py b/api/funkwhale_api/common/utils.py
index 4bb18c403a4d536a7d7245b38d21d4fac78a782b..f3f3cc0f1b56d9c300a6e5ea52863df106895c55 100644
--- a/api/funkwhale_api/common/utils.py
+++ b/api/funkwhale_api/common/utils.py
@@ -1,5 +1,7 @@
 from django.utils.deconstruct import deconstructible
 
+import bleach.sanitizer
+import markdown
 import os
 import shutil
 import uuid
@@ -241,3 +243,65 @@ def join_queries_or(left, right):
         return left | right
     else:
         return right
+
+
+def render_markdown(text):
+    return markdown.markdown(text, extensions=["nl2br"])
+
+
+HTMl_CLEANER = bleach.sanitizer.Cleaner(
+    strip=True,
+    tags=[
+        "p",
+        "a",
+        "abbr",
+        "acronym",
+        "b",
+        "blockquote",
+        "code",
+        "em",
+        "i",
+        "li",
+        "ol",
+        "strong",
+        "ul",
+    ],
+)
+
+HTML_LINKER = bleach.linkifier.Linker()
+
+
+def clean_html(html):
+    return HTMl_CLEANER.clean(html)
+
+
+def render_html(text, content_type):
+    rendered = render_markdown(text)
+    if content_type == "text/html":
+        rendered = text
+    elif content_type == "text/markdown":
+        rendered = render_markdown(text)
+    else:
+        rendered = render_markdown(text)
+    rendered = HTML_LINKER.linkify(rendered)
+    return clean_html(rendered).strip().replace("\n", "")
+
+
+@transaction.atomic
+def attach_content(obj, field, content_data):
+    from . import models
+
+    existing = getattr(obj, "{}_id".format(field))
+
+    if existing:
+        getattr(obj, field).delete()
+
+    if not content_data:
+        return
+
+    content_obj = models.Content.objects.create(
+        text=content_data["text"][: models.CONTENT_TEXT_MAX_LENGTH],
+        content_type=content_data["content_type"],
+    )
+    setattr(obj, field, content_obj)
+    obj.save(update_fields=[field])
diff --git a/api/funkwhale_api/federation/serializers.py b/api/funkwhale_api/federation/serializers.py
index 6305bd073e231ae65bb675404b9234c3386d9530..2f593111f1f004d945b5a63887a48a6f26fcbef2 100644
--- a/api/funkwhale_api/federation/serializers.py
+++ b/api/funkwhale_api/federation/serializers.py
@@ -9,7 +9,7 @@ from django.db import transaction
 
 from rest_framework import serializers
 
-from funkwhale_api.common import utils as funkwhale_utils
+from funkwhale_api.common import utils as common_utils
 from funkwhale_api.common import models as common_models
 from funkwhale_api.music import licenses
 from funkwhale_api.music import models as music_models
@@ -611,9 +611,9 @@ class PaginatedCollectionSerializer(jsonld.JsonLdSerializer):
 
     def to_representation(self, conf):
         paginator = Paginator(conf["items"], conf.get("page_size", 20))
-        first = funkwhale_utils.set_query_parameter(conf["id"], page=1)
+        first = common_utils.set_query_parameter(conf["id"], page=1)
         current = first
-        last = funkwhale_utils.set_query_parameter(conf["id"], page=paginator.num_pages)
+        last = common_utils.set_query_parameter(conf["id"], page=paginator.num_pages)
         d = {
             "id": conf["id"],
             # XXX Stable release: remove the obsolete actor field
@@ -646,7 +646,7 @@ class LibrarySerializer(PaginatedCollectionSerializer):
     )
 
     class Meta:
-        jsonld_mapping = funkwhale_utils.concat_dicts(
+        jsonld_mapping = common_utils.concat_dicts(
             PAGINATED_COLLECTION_JSONLD_MAPPING,
             {
                 "name": jsonld.first_val(contexts.AS.name),
@@ -740,11 +740,11 @@ class CollectionPageSerializer(jsonld.JsonLdSerializer):
 
     def to_representation(self, conf):
         page = conf["page"]
-        first = funkwhale_utils.set_query_parameter(conf["id"], page=1)
-        last = funkwhale_utils.set_query_parameter(
+        first = common_utils.set_query_parameter(conf["id"], page=1)
+        last = common_utils.set_query_parameter(
             conf["id"], page=page.paginator.num_pages
         )
-        id = funkwhale_utils.set_query_parameter(conf["id"], page=page.number)
+        id = common_utils.set_query_parameter(conf["id"], page=page.number)
         d = {
             "id": id,
             "partOf": conf["id"],
@@ -764,12 +764,12 @@ class CollectionPageSerializer(jsonld.JsonLdSerializer):
         }
 
         if page.has_previous():
-            d["prev"] = funkwhale_utils.set_query_parameter(
+            d["prev"] = common_utils.set_query_parameter(
                 conf["id"], page=page.previous_page_number()
             )
 
         if page.has_next():
-            d["next"] = funkwhale_utils.set_query_parameter(
+            d["next"] = common_utils.set_query_parameter(
                 conf["id"], page=page.next_page_number()
             )
         d.update(get_additional_fields(conf))
@@ -784,6 +784,8 @@ MUSIC_ENTITY_JSONLD_MAPPING = {
     "musicbrainzId": jsonld.first_val(contexts.FW.musicbrainzId),
     "attributedTo": jsonld.first_id(contexts.AS.attributedTo),
     "tags": jsonld.raw(contexts.AS.tag),
+    "mediaType": jsonld.first_val(contexts.AS.mediaType),
+    "content": jsonld.first_val(contexts.AS.content),
 }
 
 
@@ -805,6 +807,28 @@ def repr_tag(tag_name):
     return {"type": "Hashtag", "name": "#{}".format(tag_name)}
 
 
+def include_content(repr, content_obj):
+    if not content_obj:
+        return
+
+    repr["content"] = common_utils.render_html(
+        content_obj.text, content_obj.content_type
+    )
+    repr["mediaType"] = "text/html"
+
+
+class TruncatedCharField(serializers.CharField):
+    def __init__(self, *args, **kwargs):
+        self.truncate_length = kwargs.pop("truncate_length")
+        super().__init__(*args, **kwargs)
+
+    def to_internal_value(self, v):
+        v = super().to_internal_value(v)
+        if v:
+            v = v[: self.truncate_length]
+        return v
+
+
 class MusicEntitySerializer(jsonld.JsonLdSerializer):
     id = serializers.URLField(max_length=500)
     published = serializers.DateTimeField()
@@ -815,13 +839,23 @@ class MusicEntitySerializer(jsonld.JsonLdSerializer):
     tags = serializers.ListField(
         child=TagSerializer(), min_length=0, required=False, allow_null=True
     )
+    mediaType = serializers.ChoiceField(
+        choices=common_models.CONTENT_TEXT_SUPPORTED_TYPES,
+        default="text/html",
+        required=False,
+    )
+    content = TruncatedCharField(
+        truncate_length=common_models.CONTENT_TEXT_MAX_LENGTH,
+        required=False,
+        allow_null=True,
+    )
 
     @transaction.atomic
     def update(self, instance, validated_data):
         attributed_to_fid = validated_data.get("attributedTo")
         if attributed_to_fid:
             validated_data["attributedTo"] = actors.get_actor(attributed_to_fid)
-        updated_fields = funkwhale_utils.get_updated_fields(
+        updated_fields = common_utils.get_updated_fields(
             self.updateable_fields, validated_data, instance
         )
         updated_fields = self.validate_updated_data(instance, updated_fields)
@@ -831,6 +865,9 @@ class MusicEntitySerializer(jsonld.JsonLdSerializer):
 
         tags = [t["name"] for t in validated_data.get("tags", []) or []]
         tags_models.set_tags(instance, *tags)
+        common_utils.attach_content(
+            instance, "description", validated_data.get("description")
+        )
         return instance
 
     def get_tags_repr(self, instance):
@@ -842,6 +879,15 @@ class MusicEntitySerializer(jsonld.JsonLdSerializer):
     def validate_updated_data(self, instance, validated_data):
         return validated_data
 
+    def validate(self, data):
+        validated_data = super().validate(data)
+        if data.get("content"):
+            validated_data["description"] = {
+                "content_type": data["mediaType"],
+                "text": data["content"],
+            }
+        return validated_data
+
 
 class ArtistSerializer(MusicEntitySerializer):
     updateable_fields = [
@@ -866,7 +912,7 @@ class ArtistSerializer(MusicEntitySerializer):
             else None,
             "tag": self.get_tags_repr(instance),
         }
-
+        include_content(d, instance.description)
         if self.context.get("include_ap_context", self.parent is None):
             d["@context"] = jsonld.get_default_context()
         return d
@@ -888,7 +934,7 @@ class AlbumSerializer(MusicEntitySerializer):
 
     class Meta:
         model = music_models.Album
-        jsonld_mapping = funkwhale_utils.concat_dicts(
+        jsonld_mapping = common_utils.concat_dicts(
             MUSIC_ENTITY_JSONLD_MAPPING,
             {
                 "released": jsonld.first_val(contexts.FW.released),
@@ -917,6 +963,7 @@ class AlbumSerializer(MusicEntitySerializer):
             else None,
             "tag": self.get_tags_repr(instance),
         }
+        include_content(d, instance.description)
         if instance.attachment_cover:
             d["cover"] = {
                 "type": "Link",
@@ -968,7 +1015,7 @@ class TrackSerializer(MusicEntitySerializer):
 
     class Meta:
         model = music_models.Track
-        jsonld_mapping = funkwhale_utils.concat_dicts(
+        jsonld_mapping = common_utils.concat_dicts(
             MUSIC_ENTITY_JSONLD_MAPPING,
             {
                 "album": jsonld.first_obj(contexts.FW.album),
@@ -1006,7 +1053,7 @@ class TrackSerializer(MusicEntitySerializer):
             else None,
             "tag": self.get_tags_repr(instance),
         }
-
+        include_content(d, instance.description)
         if self.context.get("include_ap_context", self.parent is None):
             d["@context"] = jsonld.get_default_context()
         return d
@@ -1017,23 +1064,21 @@ class TrackSerializer(MusicEntitySerializer):
         references = {}
         actors_to_fetch = set()
         actors_to_fetch.add(
-            funkwhale_utils.recursive_getattr(
+            common_utils.recursive_getattr(
                 validated_data, "attributedTo", permissive=True
             )
         )
         actors_to_fetch.add(
-            funkwhale_utils.recursive_getattr(
+            common_utils.recursive_getattr(
                 validated_data, "album.attributedTo", permissive=True
             )
         )
         artists = (
-            funkwhale_utils.recursive_getattr(
-                validated_data, "artists", permissive=True
-            )
+            common_utils.recursive_getattr(validated_data, "artists", permissive=True)
             or []
         )
         album_artists = (
-            funkwhale_utils.recursive_getattr(
+            common_utils.recursive_getattr(
                 validated_data, "album.artists", permissive=True
             )
             or []
@@ -1244,6 +1289,7 @@ class ChannelUploadSerializer(serializers.Serializer):
                 },
             ],
         }
+        include_content(data, upload.track.description)
         tags = [item.tag.name for item in upload.get_all_tagged_items()]
         if tags:
             data["tag"] = [repr_tag(name) for name in tags]
diff --git a/api/funkwhale_api/federation/views.py b/api/funkwhale_api/federation/views.py
index f0ac6687dbb789ccf281417f141cd4b5387894b4..b732712dc6937282f14641d29179b87c00fd4b25 100644
--- a/api/funkwhale_api/federation/views.py
+++ b/api/funkwhale_api/federation/views.py
@@ -225,11 +225,14 @@ class MusicLibraryViewSet(
                         "album__attributed_to",
                         "attributed_to",
                         "album__attachment_cover",
+                        "description",
                     ).prefetch_related(
                         "tagged_items__tag",
                         "album__tagged_items__tag",
                         "album__artist__tagged_items__tag",
                         "artist__tagged_items__tag",
+                        "artist__description",
+                        "album__description",
                     ),
                 )
             ),
@@ -278,6 +281,7 @@ class MusicUploadViewSet(
         "library__actor",
         "track__artist",
         "track__album__artist",
+        "track__description",
         "track__album__attachment_cover",
     )
     serializer_class = serializers.UploadSerializer
@@ -299,7 +303,7 @@ class MusicArtistViewSet(
 ):
     authentication_classes = [authentication.SignatureAuthentication]
     renderer_classes = renderers.get_ap_renderers()
-    queryset = music_models.Artist.objects.local()
+    queryset = music_models.Artist.objects.local().select_related("description")
     serializer_class = serializers.ArtistSerializer
     lookup_field = "uuid"
 
@@ -309,7 +313,9 @@ class MusicAlbumViewSet(
 ):
     authentication_classes = [authentication.SignatureAuthentication]
     renderer_classes = renderers.get_ap_renderers()
-    queryset = music_models.Album.objects.local().select_related("artist")
+    queryset = music_models.Album.objects.local().select_related(
+        "artist__description", "description"
+    )
     serializer_class = serializers.AlbumSerializer
     lookup_field = "uuid"
 
@@ -320,7 +326,7 @@ class MusicTrackViewSet(
     authentication_classes = [authentication.SignatureAuthentication]
     renderer_classes = renderers.get_ap_renderers()
     queryset = music_models.Track.objects.local().select_related(
-        "album__artist", "artist"
+        "album__artist", "album__description", "artist__description", "description"
     )
     serializer_class = serializers.TrackSerializer
     lookup_field = "uuid"
diff --git a/api/funkwhale_api/manage/serializers.py b/api/funkwhale_api/manage/serializers.py
index dfa04bbe0120e424188aab214021392b4d92df01..20c1d023861836669f452eff8b04ac84d07716fb 100644
--- a/api/funkwhale_api/manage/serializers.py
+++ b/api/funkwhale_api/manage/serializers.py
@@ -383,7 +383,9 @@ class ManageNestedAlbumSerializer(ManageBaseAlbumSerializer):
         return getattr(obj, "tracks_count", None)
 
 
-class ManageArtistSerializer(ManageBaseArtistSerializer):
+class ManageArtistSerializer(
+    music_serializers.OptionalDescriptionMixin, ManageBaseArtistSerializer
+):
     albums = ManageNestedAlbumSerializer(many=True)
     tracks = ManageNestedTrackSerializer(many=True)
     attributed_to = ManageBaseActorSerializer()
@@ -407,7 +409,9 @@ class ManageNestedArtistSerializer(ManageBaseArtistSerializer):
     pass
 
 
-class ManageAlbumSerializer(ManageBaseAlbumSerializer):
+class ManageAlbumSerializer(
+    music_serializers.OptionalDescriptionMixin, ManageBaseAlbumSerializer
+):
     tracks = ManageNestedTrackSerializer(many=True)
     attributed_to = ManageBaseActorSerializer()
     artist = ManageNestedArtistSerializer()
@@ -435,7 +439,9 @@ class ManageTrackAlbumSerializer(ManageBaseAlbumSerializer):
         fields = ManageBaseAlbumSerializer.Meta.fields + ["artist"]
 
 
-class ManageTrackSerializer(ManageNestedTrackSerializer):
+class ManageTrackSerializer(
+    music_serializers.OptionalDescriptionMixin, ManageNestedTrackSerializer
+):
     artist = ManageNestedArtistSerializer()
     album = ManageTrackAlbumSerializer()
     attributed_to = ManageBaseActorSerializer()
diff --git a/api/funkwhale_api/manage/views.py b/api/funkwhale_api/manage/views.py
index c946c37e28bb0b749b25c06ac1c9e7f2284934c6..1754a8970a3cf904c90ec235fad2b71ddf70dc48 100644
--- a/api/funkwhale_api/manage/views.py
+++ b/api/funkwhale_api/manage/views.py
@@ -100,6 +100,11 @@ class ManageArtistViewSet(
         result = serializer.save()
         return response.Response(result, status=200)
 
+    def get_serializer_context(self):
+        context = super().get_serializer_context()
+        context["description"] = self.action in ["retrieve", "create", "update"]
+        return context
+
 
 class ManageAlbumViewSet(
     mixins.ListModelMixin,
@@ -134,6 +139,11 @@ class ManageAlbumViewSet(
         result = serializer.save()
         return response.Response(result, status=200)
 
+    def get_serializer_context(self):
+        context = super().get_serializer_context()
+        context["description"] = self.action in ["retrieve", "create", "update"]
+        return context
+
 
 uploads_subquery = (
     music_models.Upload.objects.filter(track_id=OuterRef("pk"))
@@ -186,6 +196,11 @@ class ManageTrackViewSet(
         result = serializer.save()
         return response.Response(result, status=200)
 
+    def get_serializer_context(self):
+        context = super().get_serializer_context()
+        context["description"] = self.action in ["retrieve", "create", "update"]
+        return context
+
 
 uploads_subquery = (
     music_models.Upload.objects.filter(library_id=OuterRef("pk"))
diff --git a/api/funkwhale_api/music/metadata.py b/api/funkwhale_api/music/metadata.py
index 33cf2667ead03dd0eeb4dfd7c2f79ef6be0ec0c4..78ca79168c302bf8b4776851f1d57226b410ca5b 100644
--- a/api/funkwhale_api/music/metadata.py
+++ b/api/funkwhale_api/music/metadata.py
@@ -168,6 +168,17 @@ def get_mp3_recording_id(f, k):
         raise TagNotFound(k)
 
 
+def get_mp3_comment(f, k):
+    keys_to_try = ["COMM", "COMM::eng"]
+    for key in keys_to_try:
+        try:
+            return get_id3_tag(f, key)
+        except TagNotFound:
+            pass
+
+    raise TagNotFound("COMM")
+
+
 VALIDATION = {}
 
 CONF = {
@@ -192,6 +203,7 @@ CONF = {
                 "field": "metadata_block_picture",
                 "to_application": clean_ogg_pictures,
             },
+            "comment": {"field": "comment"},
         },
     },
     "OggVorbis": {
@@ -215,6 +227,7 @@ CONF = {
                 "field": "metadata_block_picture",
                 "to_application": clean_ogg_pictures,
             },
+            "comment": {"field": "comment"},
         },
     },
     "OggTheora": {
@@ -234,6 +247,7 @@ CONF = {
             "license": {},
             "copyright": {},
             "genre": {},
+            "comment": {"field": "comment"},
         },
     },
     "MP3": {
@@ -255,6 +269,7 @@ CONF = {
             "pictures": {},
             "license": {"field": "WCOP"},
             "copyright": {"field": "TCOP"},
+            "comment": {"field": "COMM", "getter": get_mp3_comment},
         },
     },
     "MP4": {
@@ -282,6 +297,7 @@ CONF = {
             "pictures": {},
             "license": {"field": "----:com.apple.iTunes:LICENSE"},
             "copyright": {"field": "cprt"},
+            "comment": {"field": "©cmt"},
         },
     },
     "FLAC": {
@@ -304,6 +320,7 @@ CONF = {
             "pictures": {},
             "license": {},
             "copyright": {},
+            "comment": {},
         },
     },
 }
@@ -322,6 +339,7 @@ ALL_FIELDS = [
     "mbid",
     "license",
     "copyright",
+    "comment",
 ]
 
 
@@ -657,6 +675,21 @@ class PositionField(serializers.CharField):
             pass
 
 
+class DescriptionField(serializers.CharField):
+    def get_value(self, data):
+        return data
+
+    def to_internal_value(self, data):
+        try:
+            value = data.get("comment") or None
+        except TagNotFound:
+            return None
+        if not value:
+            return None
+        value = super().to_internal_value(value)
+        return {"text": value, "content_type": "text/plain"}
+
+
 class TrackMetadataSerializer(serializers.Serializer):
     title = serializers.CharField()
     position = PositionField(allow_blank=True, allow_null=True, required=False)
@@ -665,6 +698,7 @@ class TrackMetadataSerializer(serializers.Serializer):
     license = serializers.CharField(allow_blank=True, allow_null=True, required=False)
     mbid = MBIDField()
     tags = TagsField(allow_blank=True, allow_null=True, required=False)
+    description = DescriptionField(allow_null=True, allow_blank=True, required=False)
 
     album = AlbumField()
     artists = ArtistField()
@@ -672,6 +706,7 @@ class TrackMetadataSerializer(serializers.Serializer):
 
     remove_blank_null_fields = [
         "copyright",
+        "description",
         "license",
         "position",
         "disc_number",
diff --git a/api/funkwhale_api/music/migrations/0046_auto_20200113_1018.py b/api/funkwhale_api/music/migrations/0046_auto_20200113_1018.py
new file mode 100644
index 0000000000000000000000000000000000000000..1e8183317134bf09005a5f5ae4a804daa03dcdc6
--- /dev/null
+++ b/api/funkwhale_api/music/migrations/0046_auto_20200113_1018.py
@@ -0,0 +1,30 @@
+# Generated by Django 2.2.7 on 2020-01-13 10:18
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('common', '0006_content'),
+        ('music', '0045_full_text_search_stop_words'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='album',
+            name='description',
+            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='common.Content'),
+        ),
+        migrations.AddField(
+            model_name='artist',
+            name='description',
+            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='common.Content'),
+        ),
+        migrations.AddField(
+            model_name='track',
+            name='description',
+            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='common.Content'),
+        ),
+    ]
diff --git a/api/funkwhale_api/music/models.py b/api/funkwhale_api/music/models.py
index 424a307ebbfbc5f2c1ef18031d01e3f2460a22fd..9165e4658ed320c46d0348c709eaeb16c491fbb8 100644
--- a/api/funkwhale_api/music/models.py
+++ b/api/funkwhale_api/music/models.py
@@ -227,6 +227,9 @@ class Artist(APIModelMixin):
         content_type_field="object_content_type",
         object_id_field="object_id",
     )
+    description = models.ForeignKey(
+        "common.Content", null=True, blank=True, on_delete=models.SET_NULL
+    )
 
     api = musicbrainz.api.artists
     objects = ArtistQuerySet.as_manager()
@@ -327,6 +330,10 @@ class Album(APIModelMixin):
         object_id_field="object_id",
     )
 
+    description = models.ForeignKey(
+        "common.Content", null=True, blank=True, on_delete=models.SET_NULL
+    )
+
     api_includes = ["artist-credits", "recordings", "media", "release-groups"]
     api = musicbrainz.api.releases
     federation_namespace = "albums"
@@ -508,6 +515,10 @@ class Track(APIModelMixin):
     copyright = models.CharField(
         max_length=MAX_LENGTHS["COPYRIGHT"], null=True, blank=True
     )
+    description = models.ForeignKey(
+        "common.Content", null=True, blank=True, on_delete=models.SET_NULL
+    )
+
     federation_namespace = "tracks"
     musicbrainz_model = "recording"
     api = musicbrainz.api.recordings
diff --git a/api/funkwhale_api/music/mutations.py b/api/funkwhale_api/music/mutations.py
index aad3b0e9654381ce9b4b342ed00ac0fed09ab720..d158857f06b98c58d50eb3aca1d2181d4e8b2b8b 100644
--- a/api/funkwhale_api/music/mutations.py
+++ b/api/funkwhale_api/music/mutations.py
@@ -1,6 +1,8 @@
 from funkwhale_api.common import models as common_models
 from funkwhale_api.common import mutations
 from funkwhale_api.common import serializers as common_serializers
+from funkwhale_api.common import utils as common_utils
+
 from funkwhale_api.federation import routes
 from funkwhale_api.tags import models as tags_models
 from funkwhale_api.tags import serializers as tags_serializers
@@ -23,11 +25,13 @@ def can_approve(obj, actor):
 
 class TagMutation(mutations.UpdateMutationSerializer):
     tags = tags_serializers.TagsListField()
-    previous_state_handlers = {
-        "tags": lambda obj: list(
+
+    def get_previous_state_handlers(self):
+        handlers = super().get_previous_state_handlers()
+        handlers["tags"] = lambda obj: list(
             sorted(obj.tagged_items.values_list("tag__name", flat=True))
         )
-    }
+        return handlers
 
     def update(self, instance, validated_data):
         tags = validated_data.pop("tags", [])
@@ -36,17 +40,36 @@ class TagMutation(mutations.UpdateMutationSerializer):
         return r
 
 
+class DescriptionMutation(mutations.UpdateMutationSerializer):
+    description = common_serializers.ContentSerializer()
+
+    def get_previous_state_handlers(self):
+        handlers = super().get_previous_state_handlers()
+        handlers["description"] = (
+            lambda obj: common_serializers.ContentSerializer(obj.description).data
+            if obj.description_id
+            else None
+        )
+        return handlers
+
+    def update(self, instance, validated_data):
+        description = validated_data.pop("description", None)
+        r = super().update(instance, validated_data)
+        common_utils.attach_content(instance, "description", description)
+        return r
+
+
 @mutations.registry.connect(
     "update",
     models.Track,
     perm_checkers={"suggest": can_suggest, "approve": can_approve},
 )
-class TrackMutationSerializer(TagMutation):
+class TrackMutationSerializer(TagMutation, DescriptionMutation):
     serialized_relations = {"license": "code"}
 
     class Meta:
         model = models.Track
-        fields = ["license", "title", "position", "copyright", "tags"]
+        fields = ["license", "title", "position", "copyright", "tags", "description"]
 
     def post_apply(self, obj, validated_data):
         routes.outbox.dispatch(
@@ -59,10 +82,10 @@ class TrackMutationSerializer(TagMutation):
     models.Artist,
     perm_checkers={"suggest": can_suggest, "approve": can_approve},
 )
-class ArtistMutationSerializer(TagMutation):
+class ArtistMutationSerializer(TagMutation, DescriptionMutation):
     class Meta:
         model = models.Artist
-        fields = ["name", "tags"]
+        fields = ["name", "tags", "description"]
 
     def post_apply(self, obj, validated_data):
         routes.outbox.dispatch(
@@ -75,27 +98,23 @@ class ArtistMutationSerializer(TagMutation):
     models.Album,
     perm_checkers={"suggest": can_suggest, "approve": can_approve},
 )
-class AlbumMutationSerializer(TagMutation):
+class AlbumMutationSerializer(TagMutation, DescriptionMutation):
     cover = common_serializers.RelatedField(
         "uuid", queryset=common_models.Attachment.objects.all().local(), serializer=None
     )
 
     serialized_relations = {"cover": "uuid"}
-    previous_state_handlers = dict(
-        list(TagMutation.previous_state_handlers.items())
-        + [
-            (
-                "cover",
-                lambda obj: str(obj.attachment_cover.uuid)
-                if obj.attachment_cover
-                else None,
-            ),
-        ]
-    )
 
     class Meta:
         model = models.Album
-        fields = ["title", "release_date", "tags", "cover"]
+        fields = ["title", "release_date", "tags", "cover", "description"]
+
+    def get_previous_state_handlers(self):
+        handlers = super().get_previous_state_handlers()
+        handlers["cover"] = (
+            lambda obj: str(obj.attachment_cover.uuid) if obj.attachment_cover else None
+        )
+        return handlers
 
     def post_apply(self, obj, validated_data):
         routes.outbox.dispatch(
diff --git a/api/funkwhale_api/music/serializers.py b/api/funkwhale_api/music/serializers.py
index 4e45190e97c92b2fb7e34876d7c283ce97ec73aa..1049b3d9fb8370887151f90088936917e097f453 100644
--- a/api/funkwhale_api/music/serializers.py
+++ b/api/funkwhale_api/music/serializers.py
@@ -49,6 +49,20 @@ def serialize_attributed_to(self, obj):
     return federation_serializers.APIActorSerializer(obj.attributed_to).data
 
 
+class OptionalDescriptionMixin(object):
+    def to_representation(self, obj):
+        repr = super().to_representation(obj)
+        if self.context.get("description", False):
+            description = obj.description
+            repr["description"] = (
+                common_serializers.ContentSerializer(description).data
+                if description
+                else None
+            )
+
+        return repr
+
+
 class LicenseSerializer(serializers.Serializer):
     id = serializers.SerializerMethodField()
     url = serializers.URLField()
@@ -96,7 +110,7 @@ class ArtistAlbumSerializer(serializers.Serializer):
 DATETIME_FIELD = serializers.DateTimeField()
 
 
-class ArtistWithAlbumsSerializer(serializers.Serializer):
+class ArtistWithAlbumsSerializer(OptionalDescriptionMixin, serializers.Serializer):
     albums = ArtistAlbumSerializer(many=True)
     tags = serializers.SerializerMethodField()
     attributed_to = serializers.SerializerMethodField()
@@ -152,7 +166,7 @@ def serialize_album_track(track):
     }
 
 
-class AlbumSerializer(serializers.Serializer):
+class AlbumSerializer(OptionalDescriptionMixin, serializers.Serializer):
     tracks = serializers.SerializerMethodField()
     artist = serializers.SerializerMethodField()
     cover = cover_field
@@ -225,7 +239,7 @@ def serialize_upload(upload):
     }
 
 
-class TrackSerializer(serializers.Serializer):
+class TrackSerializer(OptionalDescriptionMixin, serializers.Serializer):
     artist = serializers.SerializerMethodField()
     album = TrackAlbumSerializer(read_only=True)
     uploads = serializers.SerializerMethodField()
diff --git a/api/funkwhale_api/music/tasks.py b/api/funkwhale_api/music/tasks.py
index 71f7106868f67a2e714e8655d7fff8b5c5690ea3..e6b63207249af27472ffd68210d2d840fe13ad31 100644
--- a/api/funkwhale_api/music/tasks.py
+++ b/api/funkwhale_api/music/tasks.py
@@ -12,6 +12,7 @@ from musicbrainzngs import ResponseError
 from requests.exceptions import RequestException
 
 from funkwhale_api.common import channels, preferences
+from funkwhale_api.common import utils as common_utils
 from funkwhale_api.federation import routes
 from funkwhale_api.federation import library as lb
 from funkwhale_api.federation import utils as federation_utils
@@ -309,6 +310,7 @@ def federation_audio_track_to_metadata(payload, references):
         "disc_number": payload.get("disc"),
         "license": payload.get("license"),
         "copyright": payload.get("copyright"),
+        "description": payload.get("description"),
         "attributed_to": references.get(payload.get("attributedTo")),
         "mbid": str(payload.get("musicbrainzId"))
         if payload.get("musicbrainzId")
@@ -317,6 +319,7 @@ def federation_audio_track_to_metadata(payload, references):
             "title": payload["album"]["name"],
             "fdate": payload["album"]["published"],
             "fid": payload["album"]["id"],
+            "description": payload["album"].get("description"),
             "attributed_to": references.get(payload["album"].get("attributedTo")),
             "mbid": str(payload["album"]["musicbrainzId"])
             if payload["album"].get("musicbrainzId")
@@ -328,6 +331,7 @@ def federation_audio_track_to_metadata(payload, references):
                     "fid": a["id"],
                     "name": a["name"],
                     "fdate": a["published"],
+                    "description": a.get("description"),
                     "attributed_to": references.get(a.get("attributedTo")),
                     "mbid": str(a["musicbrainzId"]) if a.get("musicbrainzId") else None,
                     "tags": [t["name"] for t in a.get("tags", []) or []],
@@ -340,6 +344,7 @@ def federation_audio_track_to_metadata(payload, references):
                 "fid": a["id"],
                 "name": a["name"],
                 "fdate": a["published"],
+                "description": a.get("description"),
                 "attributed_to": references.get(a.get("attributedTo")),
                 "mbid": str(a["musicbrainzId"]) if a.get("musicbrainzId") else None,
                 "tags": [t["name"] for t in a.get("tags", []) or []],
@@ -505,6 +510,9 @@ def _get_track(data, attributed_to=None, **forced_values):
         )
         if created:
             tags_models.add_tags(artist, *artist_data.get("tags", []))
+            common_utils.attach_content(
+                artist, "description", artist_data.get("description")
+            )
 
     if "album" in forced_values:
         album = forced_values["album"]
@@ -539,6 +547,9 @@ def _get_track(data, attributed_to=None, **forced_values):
             )
             if created:
                 tags_models.add_tags(album_artist, *album_artist_data.get("tags", []))
+                common_utils.attach_content(
+                    album_artist, "description", album_artist_data.get("description")
+                )
 
         # get / create album
         album_data = data["album"]
@@ -569,6 +580,9 @@ def _get_track(data, attributed_to=None, **forced_values):
         )
         if created:
             tags_models.add_tags(album, *album_data.get("tags", []))
+            common_utils.attach_content(
+                album, "description", album_data.get("description")
+            )
 
     # get / create track
     track_title = (
@@ -602,6 +616,7 @@ def _get_track(data, attributed_to=None, **forced_values):
         query |= Q(mbid=track_mbid)
     if track_fid:
         query |= Q(fid=track_fid)
+
     defaults = {
         "title": track_title,
         "album": album,
@@ -627,6 +642,8 @@ def _get_track(data, attributed_to=None, **forced_values):
             forced_values["tags"] if "tags" in forced_values else data.get("tags", [])
         )
         tags_models.add_tags(track, *tags)
+        common_utils.attach_content(track, "description", data.get("description"))
+
     return track
 
 
diff --git a/api/funkwhale_api/music/views.py b/api/funkwhale_api/music/views.py
index 39c327e373016a1fdcef6d93d48cd4b63f65132b..f7fb6e653b80576bc82d71b898e688b23dc659ab 100644
--- a/api/funkwhale_api/music/views.py
+++ b/api/funkwhale_api/music/views.py
@@ -143,6 +143,11 @@ class ArtistViewSet(
             obj = refetch_obj(obj, self.get_queryset())
         return obj
 
+    def get_serializer_context(self):
+        context = super().get_serializer_context()
+        context["description"] = self.action in ["retrieve", "create", "update"]
+        return context
+
     def get_queryset(self):
         queryset = super().get_queryset()
         albums = models.Album.objects.with_tracks_count().select_related(
@@ -194,6 +199,11 @@ class AlbumViewSet(
             obj = refetch_obj(obj, self.get_queryset())
         return obj
 
+    def get_serializer_context(self):
+        context = super().get_serializer_context()
+        context["description"] = self.action in ["retrieve", "create", "update"]
+        return context
+
     def get_queryset(self):
         queryset = super().get_queryset()
         tracks = (
@@ -332,6 +342,11 @@ class TrackViewSet(
         get_libraries(filter_uploads=lambda o, uploads: uploads.filter(track=o))
     )
 
+    def get_serializer_context(self):
+        context = super().get_serializer_context()
+        context["description"] = self.action in ["retrieve", "create", "update"]
+        return context
+
 
 def strip_absolute_media_url(path):
     if (
diff --git a/api/requirements/base.txt b/api/requirements/base.txt
index 599f7b9ff82961de6bb169c62abba270a091111b..9cdbb477d1b669d099e7583cec586a1e2bb30293 100644
--- a/api/requirements/base.txt
+++ b/api/requirements/base.txt
@@ -76,3 +76,5 @@ django-cacheops==4.2
 
 click>=7,<8
 service_identity==18.1.0
+markdown>=3,<4
+bleach>=3,<4
diff --git a/api/tests/common/test_models.py b/api/tests/common/test_models.py
index b5a56614d5be547b8ed04ecbbcd75b40f084d493..9fdde0378b1471babc5805cd00e42f75226fc358 100644
--- a/api/tests/common/test_models.py
+++ b/api/tests/common/test_models.py
@@ -71,3 +71,17 @@ def test_attachment_queryset_attached(args, expected, factories, queryset_equal_
     queryset = attachments[0].__class__.objects.attached(*args).order_by("id")
     expected_objs = [attachments[i] for i in expected]
     assert queryset == expected_objs
+
+
+def test_removing_obj_removes_content(factories):
+    kept_content = factories["common.Content"]()
+    removed_content = factories["common.Content"]()
+    track1 = factories["music.Track"](description=removed_content)
+    factories["music.Track"](description=kept_content)
+
+    track1.delete()
+
+    with pytest.raises(removed_content.DoesNotExist):
+        removed_content.refresh_from_db()
+
+    kept_content.refresh_from_db()
diff --git a/api/tests/common/test_serializers.py b/api/tests/common/test_serializers.py
index 067a9e4a20c7ddc0869127659a52e87286f1971d..8fdb21edbaa98e556a84fcbe2bd67aecd176d5f9 100644
--- a/api/tests/common/test_serializers.py
+++ b/api/tests/common/test_serializers.py
@@ -7,6 +7,7 @@ from django.urls import reverse
 import django_filters
 
 from funkwhale_api.common import serializers
+from funkwhale_api.common import utils
 from funkwhale_api.users import models
 from funkwhale_api.federation import utils as federation_utils
 
@@ -252,3 +253,17 @@ def test_attachment_serializer_remote_file(factories, to_api_date):
     serializer = serializers.AttachmentSerializer(attachment)
 
     assert serializer.data == expected
+
+
+def test_content_serializer(factories):
+    content = factories["common.Content"]()
+
+    expected = {
+        "text": content.text,
+        "content_type": content.content_type,
+        "html": utils.render_html(content.text, content.content_type),
+    }
+
+    serializer = serializers.ContentSerializer(content)
+
+    assert serializer.data == expected
diff --git a/api/tests/common/test_utils.py b/api/tests/common/test_utils.py
index 74a3d0bca61526e1cf7b5e87772a18a17a3a77aa..def5434a4b8c1ef3daf07bf91929b61a8216ef8a 100644
--- a/api/tests/common/test_utils.py
+++ b/api/tests/common/test_utils.py
@@ -99,3 +99,28 @@ def test_get_updated_fields(conf, mock_args, data, expected, mocker):
 )
 def test_join_url(start, end, expected):
     assert utils.join_url(start, end) == expected
+
+
+@pytest.mark.parametrize(
+    "text, content_type, expected",
+    [
+        ("hello world", "text/markdown", "<p>hello world</p>"),
+        ("hello world", "text/plain", "<p>hello world</p>"),
+        ("<strong>hello world</strong>", "text/html", "<strong>hello world</strong>"),
+        # images and other non whitelisted html should be removed
+        ("hello world\n![img](src)", "text/markdown", "<p>hello world</p>"),
+        (
+            "hello world\n\n<script></script>\n\n<style></style>",
+            "text/markdown",
+            "<p>hello world</p>",
+        ),
+        (
+            "<p>hello world</p><script></script>\n\n<style></style>",
+            "text/html",
+            "<p>hello world</p>",
+        ),
+    ],
+)
+def test_render_html(text, content_type, expected):
+    result = utils.render_html(text, content_type)
+    assert result == expected
diff --git a/api/tests/federation/test_serializers.py b/api/tests/federation/test_serializers.py
index 4076fdd5a1ca853d74d7a1b3f5881a1bda221582..e158b604186ef02e16fde04810b0a99c3c7b8437 100644
--- a/api/tests/federation/test_serializers.py
+++ b/api/tests/federation/test_serializers.py
@@ -5,6 +5,7 @@ import uuid
 from django.core.paginator import Paginator
 from django.utils import timezone
 
+from funkwhale_api.common import utils as common_utils
 from funkwhale_api.federation import contexts
 from funkwhale_api.federation import keys
 from funkwhale_api.federation import jsonld
@@ -560,7 +561,10 @@ def test_music_library_serializer_from_private(factories, mocker):
 
 
 def test_activity_pub_artist_serializer_to_ap(factories):
-    artist = factories["music.Artist"](attributed=True, set_tags=["Punk", "Rock"])
+    content = factories["common.Content"]()
+    artist = factories["music.Artist"](
+        description=content, attributed=True, set_tags=["Punk", "Rock"]
+    )
     expected = {
         "@context": jsonld.get_default_context(),
         "type": "Artist",
@@ -569,6 +573,8 @@ def test_activity_pub_artist_serializer_to_ap(factories):
         "musicbrainzId": artist.mbid,
         "published": artist.creation_date.isoformat(),
         "attributedTo": artist.attributed_to.fid,
+        "mediaType": "text/html",
+        "content": common_utils.render_html(content.text, content.content_type),
         "tag": [
             {"type": "Hashtag", "name": "#Punk"},
             {"type": "Hashtag", "name": "#Rock"},
@@ -580,7 +586,10 @@ def test_activity_pub_artist_serializer_to_ap(factories):
 
 
 def test_activity_pub_album_serializer_to_ap(factories):
-    album = factories["music.Album"](attributed=True, set_tags=["Punk", "Rock"])
+    content = factories["common.Content"]()
+    album = factories["music.Album"](
+        description=content, attributed=True, set_tags=["Punk", "Rock"]
+    )
 
     expected = {
         "@context": jsonld.get_default_context(),
@@ -601,6 +610,8 @@ def test_activity_pub_album_serializer_to_ap(factories):
             ).data
         ],
         "attributedTo": album.attributed_to.fid,
+        "mediaType": "text/html",
+        "content": common_utils.render_html(content.text, content.content_type),
         "tag": [
             {"type": "Hashtag", "name": "#Punk"},
             {"type": "Hashtag", "name": "#Rock"},
@@ -653,7 +664,9 @@ def test_activity_pub_album_serializer_from_ap_update(factories, faker):
 
 
 def test_activity_pub_track_serializer_to_ap(factories):
+    content = factories["common.Content"]()
     track = factories["music.Track"](
+        description=content,
         license="cc-by-4.0",
         copyright="test",
         disc_number=3,
@@ -680,6 +693,8 @@ def test_activity_pub_track_serializer_to_ap(factories):
             track.album, context={"include_ap_context": False}
         ).data,
         "attributedTo": track.attributed_to.fid,
+        "mediaType": "text/html",
+        "content": common_utils.render_html(content.text, content.content_type),
         "tag": [
             {"type": "Hashtag", "name": "#Punk"},
             {"type": "Hashtag", "name": "#Rock"},
@@ -709,6 +724,7 @@ def test_activity_pub_track_serializer_from_ap(factories, r_mock, mocker):
         "name": "Black in back",
         "position": 5,
         "disc": 1,
+        "content": "Hello there",
         "attributedTo": track_attributed_to.fid,
         "album": {
             "type": "Album",
@@ -717,6 +733,8 @@ def test_activity_pub_track_serializer_from_ap(factories, r_mock, mocker):
             "musicbrainzId": str(uuid.uuid4()),
             "published": published.isoformat(),
             "released": released.isoformat(),
+            "content": "Album summary",
+            "mediaType": "text/markdown",
             "attributedTo": album_attributed_to.fid,
             "cover": {
                 "type": "Link",
@@ -727,6 +745,8 @@ def test_activity_pub_track_serializer_from_ap(factories, r_mock, mocker):
             "artists": [
                 {
                     "type": "Artist",
+                    "mediaType": "text/plain",
+                    "content": "Artist summary",
                     "id": "http://hello.artist",
                     "name": "John Smith",
                     "musicbrainzId": str(uuid.uuid4()),
@@ -741,6 +761,8 @@ def test_activity_pub_track_serializer_from_ap(factories, r_mock, mocker):
                 "type": "Artist",
                 "id": "http://hello.trackartist",
                 "name": "Bob Smith",
+                "mediaType": "text/plain",
+                "content": "Other artist summary",
                 "musicbrainzId": str(uuid.uuid4()),
                 "attributedTo": artist_attributed_to.fid,
                 "published": published.isoformat(),
@@ -769,6 +791,8 @@ def test_activity_pub_track_serializer_from_ap(factories, r_mock, mocker):
     assert track.creation_date == published
     assert track.attributed_to == track_attributed_to
     assert str(track.mbid) == data["musicbrainzId"]
+    assert track.description.text == data["content"]
+    assert track.description.content_type == "text/html"
 
     assert album.from_activity == activity
     assert album.attachment_cover.file.read() == b"coucou"
@@ -779,6 +803,8 @@ def test_activity_pub_track_serializer_from_ap(factories, r_mock, mocker):
     assert album.creation_date == published
     assert album.release_date == released
     assert album.attributed_to == album_attributed_to
+    assert album.description.text == data["album"]["content"]
+    assert album.description.content_type == data["album"]["mediaType"]
 
     assert artist.from_activity == activity
     assert artist.name == data["artists"][0]["name"]
@@ -786,6 +812,8 @@ def test_activity_pub_track_serializer_from_ap(factories, r_mock, mocker):
     assert str(artist.mbid) == data["artists"][0]["musicbrainzId"]
     assert artist.creation_date == published
     assert artist.attributed_to == artist_attributed_to
+    assert artist.description.text == data["artists"][0]["content"]
+    assert artist.description.content_type == data["artists"][0]["mediaType"]
 
     assert album_artist.from_activity == activity
     assert album_artist.name == data["album"]["artists"][0]["name"]
@@ -793,6 +821,11 @@ def test_activity_pub_track_serializer_from_ap(factories, r_mock, mocker):
     assert str(album_artist.mbid) == data["album"]["artists"][0]["musicbrainzId"]
     assert album_artist.creation_date == published
     assert album_artist.attributed_to == album_artist_attributed_to
+    assert album_artist.description.text == data["album"]["artists"][0]["content"]
+    assert (
+        album_artist.description.content_type
+        == data["album"]["artists"][0]["mediaType"]
+    )
 
     add_tags.assert_any_call(track, *["Hello", "World"])
     add_tags.assert_any_call(album, *["AlbumTag"])
@@ -802,8 +835,9 @@ def test_activity_pub_track_serializer_from_ap(factories, r_mock, mocker):
 
 def test_activity_pub_track_serializer_from_ap_update(factories, r_mock, mocker):
     set_tags = mocker.patch("funkwhale_api.tags.models.set_tags")
+    content = factories["common.Content"]()
     track_attributed_to = factories["federation.Actor"]()
-    track = factories["music.Track"]()
+    track = factories["music.Track"](description=content)
 
     published = timezone.now()
     data = {
@@ -815,6 +849,7 @@ def test_activity_pub_track_serializer_from_ap_update(factories, r_mock, mocker)
         "name": "Black in back",
         "position": 5,
         "disc": 2,
+        "content": "hello there",
         "attributedTo": track_attributed_to.fid,
         "album": serializers.AlbumSerializer(track.album).data,
         "artists": [serializers.ArtistSerializer(track.artist).data],
@@ -835,10 +870,15 @@ def test_activity_pub_track_serializer_from_ap_update(factories, r_mock, mocker)
     assert track.position == data["position"]
     assert track.disc_number == data["disc"]
     assert track.attributed_to == track_attributed_to
+    assert track.description.content_type == "text/html"
+    assert track.description.text == "hello there"
     assert str(track.mbid) == data["musicbrainzId"]
 
     set_tags.assert_called_once_with(track, *["Hello", "World"])
 
+    with pytest.raises(content.DoesNotExist):
+        content.refresh_from_db()
+
 
 def test_activity_pub_upload_serializer_from_ap(factories, mocker, r_mock):
     activity = factories["federation.Activity"]()
@@ -1083,11 +1123,13 @@ def test_channel_actor_outbox_serializer(factories):
 
 def test_channel_upload_serializer(factories):
     channel = factories["audio.Channel"]()
+    content = factories["common.Content"]()
     upload = factories["music.Upload"](
         playable=True,
         library=channel.library,
         import_status="finished",
         track__set_tags=["Punk"],
+        track__description=content,
         track__album__set_tags=["Rock"],
         track__artist__set_tags=["Indie"],
     )
@@ -1100,6 +1142,8 @@ def test_channel_upload_serializer(factories):
         "summary": "#Indie #Punk #Rock",
         "attributedTo": channel.actor.fid,
         "published": upload.creation_date.isoformat(),
+        "mediaType": "text/html",
+        "content": common_utils.render_html(content.text, content.content_type),
         "to": "https://www.w3.org/ns/activitystreams#Public",
         "url": [
             {
diff --git a/api/tests/music/sample.flac b/api/tests/music/sample.flac
index a8aafa39239a199663c6673c678064826ecf965b..b89db5b8c580dacd2f954b4238d266348c96cee0 100644
Binary files a/api/tests/music/sample.flac and b/api/tests/music/sample.flac differ
diff --git a/api/tests/music/test.m4a b/api/tests/music/test.m4a
index 57b65b7b53489c85cb7a9e5ca5fa92fa976c35ea..24c49c2db1b09724aff7608663d6abd8f7b0748f 100644
Binary files a/api/tests/music/test.m4a and b/api/tests/music/test.m4a differ
diff --git a/api/tests/music/test.mp3 b/api/tests/music/test.mp3
index 5f8dc2c727d63873ed7cada78379030b9cadb0d7..5545c42f55e71637250379bb397a050de3e337f1 100644
Binary files a/api/tests/music/test.mp3 and b/api/tests/music/test.mp3 differ
diff --git a/api/tests/music/test.ogg b/api/tests/music/test.ogg
index 9975cd9fe447824358da6dc9a89780c61ade8aee..7d1f523dc4e4c44c011c2aa8d8ee2794f6d51f5f 100644
Binary files a/api/tests/music/test.ogg and b/api/tests/music/test.ogg differ
diff --git a/api/tests/music/test.opus b/api/tests/music/test.opus
index 92634ce507bbe0bc0f0d36b7ea6abe34965cafe9..c1c324bcc336b13924b64f3e266895bb9028c1a5 100644
Binary files a/api/tests/music/test.opus and b/api/tests/music/test.opus differ
diff --git a/api/tests/music/test_metadata.py b/api/tests/music/test_metadata.py
index f6aa513ab97c674f2f9a33c150b204e523dc4ef4..d842d33183780e35e2e8f2668dced3d8f19ec31c 100644
--- a/api/tests/music/test_metadata.py
+++ b/api/tests/music/test_metadata.py
@@ -30,6 +30,7 @@ DATA_DIR = os.path.dirname(os.path.abspath(__file__))
         ),
         ("license", "Dummy license: http://creativecommons.org/licenses/by-sa/4.0/"),
         ("copyright", "Someone"),
+        ("comment", "Hello there"),
     ],
 )
 def test_can_get_metadata_from_ogg_file(field, value):
@@ -58,6 +59,7 @@ def test_can_get_metadata_all():
         "license": "Dummy license: http://creativecommons.org/licenses/by-sa/4.0/",
         "copyright": "Someone",
         "genre": "Classical",
+        "comment": "Hello there",
     }
     assert data.all() == expected
 
@@ -81,6 +83,7 @@ def test_can_get_metadata_all():
         ),
         ("license", "Dummy license: http://creativecommons.org/licenses/by-sa/4.0/"),
         ("copyright", "Someone"),
+        ("comment", "Hello there"),
     ],
 )
 def test_can_get_metadata_from_opus_file(field, value):
@@ -104,6 +107,7 @@ def test_can_get_metadata_from_opus_file(field, value):
         ("mbid", "124d0150-8627-46bc-bc14-789a3bc960c8"),
         ("musicbrainz_artistid", "c3bc80a6-1f4a-4e17-8cf0-6b1efe8302f1"),
         ("musicbrainz_albumartistid", "c3bc80a6-1f4a-4e17-8cf0-6b1efe8302f1"),
+        ("comment", "Hello there"),
         # somehow, I cannot successfully create an ogg theora file
         # with the proper license field
         # ("license", "Dummy license: http://creativecommons.org/licenses/by-sa/4.0/"),
@@ -132,6 +136,7 @@ def test_can_get_metadata_from_ogg_theora_file(field, value):
         ("musicbrainz_albumartistid", "9c6bddde-6228-4d9f-ad0d-03f6fcb19e13"),
         ("license", "https://creativecommons.org/licenses/by-nc-nd/2.5/"),
         ("copyright", "Someone"),
+        ("comment", "Hello there"),
     ],
 )
 def test_can_get_metadata_from_id3_mp3_file(field, value):
@@ -181,6 +186,7 @@ def test_can_get_pictures(name):
         ("musicbrainz_albumartistid", "b7ffd2af-418f-4be2-bdd1-22f8b48613da"),
         ("license", "http://creativecommons.org/licenses/by-nc-sa/3.0/us/"),
         ("copyright", "2008 nin"),
+        ("comment", "Hello there"),
     ],
 )
 def test_can_get_metadata_from_flac_file(field, value):
@@ -210,6 +216,7 @@ def test_can_get_metadata_from_flac_file(field, value):
         ("license", "Dummy license: http://creativecommons.org/licenses/by-sa/4.0/"),
         ("copyright", "Someone"),
         ("genre", "Dubstep"),
+        ("comment", "Hello there"),
     ],
 )
 def test_can_get_metadata_from_m4a_file(field, value):
@@ -294,6 +301,7 @@ def test_metadata_fallback_ogg_theora(mocker):
                 "license": "https://creativecommons.org/licenses/by-nc-nd/2.5/",
                 "copyright": "Someone",
                 "tags": ["Funk"],
+                "description": {"text": "Hello there", "content_type": "text/plain"},
             },
         ),
         (
@@ -327,6 +335,7 @@ def test_metadata_fallback_ogg_theora(mocker):
                 "license": "Dummy license: http://creativecommons.org/licenses/by-sa/4.0/",
                 "copyright": "Someone",
                 "tags": ["Classical"],
+                "description": {"text": "Hello there", "content_type": "text/plain"},
             },
         ),
         (
@@ -360,6 +369,7 @@ def test_metadata_fallback_ogg_theora(mocker):
                 "license": "Dummy license: http://creativecommons.org/licenses/by-sa/4.0/",
                 "copyright": "Someone",
                 "tags": ["Classical"],
+                "description": {"text": "Hello there", "content_type": "text/plain"},
             },
         ),
         (
@@ -391,6 +401,7 @@ def test_metadata_fallback_ogg_theora(mocker):
                 # with the proper license field
                 # ("license", "Dummy license: http://creativecommons.org/licenses/by-sa/4.0/"),
                 "copyright": "â„— 2012 JKP GmbH & Co. KG",
+                "description": {"text": "Hello there", "content_type": "text/plain"},
             },
         ),
         (
@@ -420,6 +431,7 @@ def test_metadata_fallback_ogg_theora(mocker):
                 "license": "http://creativecommons.org/licenses/by-nc-sa/3.0/us/",
                 "copyright": "2008 nin",
                 "tags": ["Industrial"],
+                "description": {"text": "Hello there", "content_type": "text/plain"},
             },
         ),
     ],
@@ -528,10 +540,12 @@ def test_fake_metadata_with_serializer():
         "musicbrainz_albumartistid": "013c8e5b-d72a-4cd3-8dee-6c64d6125823;5b4d7d2d-36df-4b38-95e3-a964234f520f",
         "license": "Dummy license: http://creativecommons.org/licenses/by-sa/4.0/",
         "copyright": "Someone",
+        "comment": "hello there",
     }
 
     expected = {
         "title": "Peer Gynt Suite no. 1, op. 46: I. Morning",
+        "description": {"text": "hello there", "content_type": "text/plain"},
         "artists": [
             {
                 "name": "Edvard Grieg",
diff --git a/api/tests/music/test_mutations.py b/api/tests/music/test_mutations.py
index ff2982dffc3c7c1748de57daa192822b8ef712ae..eff6925d39ad0d9c3fc8b8b87ec3f9c988b9e44e 100644
--- a/api/tests/music/test_mutations.py
+++ b/api/tests/music/test_mutations.py
@@ -1,6 +1,7 @@
 import datetime
 import pytest
 
+from funkwhale_api.common import serializers as common_serializers
 from funkwhale_api.music import licenses
 from funkwhale_api.music import mutations
 
@@ -195,3 +196,26 @@ def test_mutation_set_attachment_cover(factories, now, mocker):
 
     assert obj.attachment_cover == new_attachment
     assert mutation.previous_state["cover"] == old_attachment.uuid
+
+
+@pytest.mark.parametrize(
+    "factory_name", ["music.Track", "music.Album", "music.Artist"],
+)
+def test_album_mutation_description(factory_name, factories, mocker):
+    mocker.patch("funkwhale_api.federation.routes.outbox.dispatch")
+    content = factories["common.Content"]()
+    obj = factories[factory_name](description=content)
+    mutation = factories["common.Mutation"](
+        type="update",
+        target=obj,
+        payload={"description": {"content_type": "text/plain", "text": "hello there"}},
+    )
+    mutation.apply()
+    obj.refresh_from_db()
+
+    assert obj.description.content_type == "text/plain"
+    assert obj.description.text == "hello there"
+    assert (
+        mutation.previous_state["description"]
+        == common_serializers.ContentSerializer(content).data
+    )
diff --git a/api/tests/music/test_serializers.py b/api/tests/music/test_serializers.py
index ae486997ed69799651e083f960def76e9429c382..a35180cefcb7e5c584d28a8d1a0e57bdcdbc3fcf 100644
--- a/api/tests/music/test_serializers.py
+++ b/api/tests/music/test_serializers.py
@@ -501,3 +501,21 @@ def test_upload_with_channel_validates_import_metadata(factories, uploaded_audio
     )
     with pytest.raises(serializers.serializers.ValidationError):
         assert serializer.is_valid(raise_exception=True)
+
+
+@pytest.mark.parametrize(
+    "factory_name, serializer_class",
+    [
+        ("music.Artist", serializers.ArtistWithAlbumsSerializer),
+        ("music.Album", serializers.AlbumSerializer),
+        ("music.Track", serializers.TrackSerializer),
+    ],
+)
+def test_detail_serializers_with_description_description(
+    factory_name, serializer_class, factories
+):
+    content = factories["common.Content"]()
+    obj = factories[factory_name](description=content)
+    expected = common_serializers.ContentSerializer(content).data
+    serializer = serializer_class(obj, context={"description": True})
+    assert serializer.data["description"] == expected
diff --git a/api/tests/music/test_tasks.py b/api/tests/music/test_tasks.py
index 449be0c04b17b7a3ce765ee4d2cb18eed4ea7cce..3f09c537838667c12fa382351bcb6b762300ebcc 100644
--- a/api/tests/music/test_tasks.py
+++ b/api/tests/music/test_tasks.py
@@ -128,6 +128,21 @@ def test_can_create_track_from_file_metadata_featuring(factories):
     assert track.artist.name == "Santana feat. Chris Cornell"
 
 
+def test_can_create_track_from_file_metadata_description(factories):
+    metadata = {
+        "title": "Whole Lotta Love",
+        "position": 1,
+        "disc_number": 1,
+        "description": {"text": "hello there", "content_type": "text/plain"},
+        "album": {"title": "Test album"},
+        "artists": [{"name": "Santana"}],
+    }
+    track = tasks.get_track_from_import_metadata(metadata)
+
+    assert track.description.text == "hello there"
+    assert track.description.content_type == "text/plain"
+
+
 def test_can_create_track_from_file_metadata_mbid(factories, mocker):
     metadata = {
         "title": "Test track",
@@ -607,6 +622,7 @@ def test_federation_audio_track_to_metadata(now, mocker):
         "copyright": "2018 Someone",
         "attributedTo": "http://track.attributed",
         "tag": [{"type": "Hashtag", "name": "TrackTag"}],
+        "content": "hello there",
         "album": {
             "published": published.isoformat(),
             "type": "Album",
@@ -616,12 +632,16 @@ def test_federation_audio_track_to_metadata(now, mocker):
             "released": released.isoformat(),
             "tag": [{"type": "Hashtag", "name": "AlbumTag"}],
             "attributedTo": "http://album.attributed",
+            "content": "album desc",
+            "mediaType": "text/plain",
             "artists": [
                 {
                     "type": "Artist",
                     "published": published.isoformat(),
                     "id": "http://hello.artist",
                     "name": "John Smith",
+                    "content": "album artist desc",
+                    "mediaType": "text/markdown",
                     "musicbrainzId": str(uuid.uuid4()),
                     "attributedTo": "http://album-artist.attributed",
                     "tag": [{"type": "Hashtag", "name": "AlbumArtistTag"}],
@@ -639,6 +659,8 @@ def test_federation_audio_track_to_metadata(now, mocker):
                 "type": "Artist",
                 "id": "http://hello.trackartist",
                 "name": "Bob Smith",
+                "content": "artist desc",
+                "mediaType": "text/html",
                 "musicbrainzId": str(uuid.uuid4()),
                 "attributedTo": "http://artist.attributed",
                 "tag": [{"type": "Hashtag", "name": "ArtistTag"}],
@@ -658,6 +680,7 @@ def test_federation_audio_track_to_metadata(now, mocker):
         "fid": payload["id"],
         "attributed_to": references["http://track.attributed"],
         "tags": ["TrackTag"],
+        "description": {"content_type": "text/html", "text": "hello there"},
         "album": {
             "title": payload["album"]["name"],
             "attributed_to": references["http://album.attributed"],
@@ -666,6 +689,7 @@ def test_federation_audio_track_to_metadata(now, mocker):
             "fid": payload["album"]["id"],
             "fdate": serializer.validated_data["album"]["published"],
             "tags": ["AlbumTag"],
+            "description": {"content_type": "text/plain", "text": "album desc"},
             "artists": [
                 {
                     "name": a["name"],
@@ -675,6 +699,10 @@ def test_federation_audio_track_to_metadata(now, mocker):
                     "fdate": serializer.validated_data["album"]["artists"][i][
                         "published"
                     ],
+                    "description": {
+                        "content_type": "text/markdown",
+                        "text": "album artist desc",
+                    },
                     "tags": ["AlbumArtistTag"],
                 }
                 for i, a in enumerate(payload["album"]["artists"])
@@ -690,6 +718,7 @@ def test_federation_audio_track_to_metadata(now, mocker):
                 "fdate": serializer.validated_data["artists"][i]["published"],
                 "attributed_to": references["http://artist.attributed"],
                 "tags": ["ArtistTag"],
+                "description": {"content_type": "text/html", "text": "artist desc"},
             }
             for i, a in enumerate(payload["artists"])
         ],
diff --git a/api/tests/music/test_theora.ogg b/api/tests/music/test_theora.ogg
index 3aa7117387a87115d9316f9d34205254381c95ec..ae21c94bc8a505a8c9e3f16c2bb9f7678dce452c 100644
Binary files a/api/tests/music/test_theora.ogg and b/api/tests/music/test_theora.ogg differ
diff --git a/api/tests/music/test_views.py b/api/tests/music/test_views.py
index 744edd2d05ddbff8cde7cfac02486cccdc83c1a8..f78e6988c9aee7a0bd72d669e0b4ea3f8db39d5a 100644
--- a/api/tests/music/test_views.py
+++ b/api/tests/music/test_views.py
@@ -1256,3 +1256,22 @@ def test_search_get_fts_stop_words(settings, logged_in_api_client, factories):
 
     assert response.status_code == 200
     assert response.data == expected
+
+
+@pytest.mark.parametrize(
+    "route, factory_name",
+    [
+        ("api:v1:artists-detail", "music.Artist"),
+        ("api:v1:albums-detail", "music.Album"),
+        ("api:v1:tracks-detail", "music.Track"),
+    ],
+)
+def test_detail_includes_description_key(
+    route, factory_name, logged_in_api_client, factories
+):
+    obj = factories[factory_name]()
+    url = reverse(route, kwargs={"pk": obj.pk})
+
+    response = logged_in_api_client.get(url)
+
+    assert response.data["description"] is None
diff --git a/front/src/components/library/EditForm.vue b/front/src/components/library/EditForm.vue
index 6f4afccacbe968a19aa13bf4d2914b6b41e7d894..70f83d0496b2524648531f4130c24a35a7af1727 100644
--- a/front/src/components/library/EditForm.vue
+++ b/front/src/components/library/EditForm.vue
@@ -77,6 +77,10 @@
           </button>
 
         </template>
+        <template v-else-if="fieldConfig.type === 'content'">
+          <label :for="fieldConfig.id">{{ fieldConfig.label }}</label>
+          <textarea v-model="values[fieldConfig.id].text" :name="fieldConfig.id" :id="fieldConfig.id" rows="3"></textarea>
+        </template>
         <template v-else-if="fieldConfig.type === 'attachment'">
           <label :for="fieldConfig.id">{{ fieldConfig.label }}</label>
           <attachment-input
@@ -100,8 +104,8 @@
             <translate translate-context="Content/Library/Button.Label">Clear</translate>
           </button>
         </template>
-        <div v-if="values[fieldConfig.id] != initialValues[fieldConfig.id]">
-          <button class="ui tiny basic right floated reset button" form="noop" @click.prevent="values[fieldConfig.id] = initialValues[fieldConfig.id]">
+        <div v-if="!lodash.isEqual(values[fieldConfig.id], initialValues[fieldConfig.id])">
+          <button class="ui tiny basic right floated reset button" form="noop" @click.prevent="values[fieldConfig.id] = lodash.clone(initialValues[fieldConfig.id])">
             <i class="undo icon"></i>
             <translate translate-context="Content/Library/Button.Label">Reset to initial value</translate>
           </button>
@@ -156,6 +160,7 @@ export default {
       summary: '',
       submittedMutation: null,
       showPendingReview: true,
+      lodash,
     }
   },
   created () {
@@ -216,8 +221,8 @@ export default {
     setValues () {
       let self = this
       this.config.fields.forEach(f => {
-        self.$set(self.values, f.id, f.getValue(self.object))
-        self.$set(self.initialValues, f.id, self.values[f.id])
+        self.$set(self.values, f.id, lodash.clone(f.getValue(self.object)))
+        self.$set(self.initialValues, f.id, lodash.clone(self.values[f.id]))
       })
     },
     submit() {
diff --git a/front/src/edits.js b/front/src/edits.js
index 8449677129d02b3396ed15fdeb8cf5b881547e3c..0757ed0d4a169873984e0ebc192beac14ef74a41 100644
--- a/front/src/edits.js
+++ b/front/src/edits.js
@@ -5,8 +5,20 @@ function getTagsValueRepr (val) {
   return val.slice().sort().join('\n')
 }
 
+function getContentValueRepr (val) {
+  return val.text
+}
+
 export default {
   getConfigs () {
+    const description = {
+      id: 'description',
+      type: 'content',
+      required: true,
+      label: this.$pgettext('*/*/*/Noun', 'Description'),
+      getValue: (obj) => { return obj.description || {text: null, content_type: 'text/markdown'}},
+      getValueRepr: getContentValueRepr
+    }
     return {
       artist: {
         fields: [
@@ -17,6 +29,7 @@ export default {
             label: this.$pgettext('*/*/*/Noun', 'Name'),
             getValue: (obj) => { return obj.name }
           },
+          description,
           {
             id: 'tags',
             type: 'tags',
@@ -24,7 +37,7 @@ export default {
             label: this.$pgettext('*/*/*/Noun', 'Tags'),
             getValue: (obj) => { return obj.tags },
             getValueRepr: getTagsValueRepr
-          }
+          },
         ]
       },
       album: {
@@ -36,6 +49,7 @@ export default {
             label: this.$pgettext('*/*/*/Noun', 'Title'),
             getValue: (obj) => { return obj.title }
           },
+          description,
           {
             id: 'release_date',
             type: 'text',
@@ -75,6 +89,7 @@ export default {
             label: this.$pgettext('*/*/*/Noun', 'Title'),
             getValue: (obj) => { return obj.title }
           },
+          description,
           {
             id: 'position',
             type: 'text',
diff --git a/front/src/views/admin/library/AlbumDetail.vue b/front/src/views/admin/library/AlbumDetail.vue
index fdfc4c7faab2aa11efe793534368e9c99a1b1a99..6ca2acf8812123955462dfeb7f567406c0009daf 100644
--- a/front/src/views/admin/library/AlbumDetail.vue
+++ b/front/src/views/admin/library/AlbumDetail.vue
@@ -129,6 +129,12 @@
                       {{ object.domain }}
                     </td>
                   </tr>
+                  <tr v-if="object.description">
+                    <td>
+                      <translate translate-context="'*/*/*/Noun">Description</translate>
+                    </td>
+                    <td v-html="object.description.html"></td>
+                  </tr>
                 </tbody>
               </table>
             </section>
diff --git a/front/src/views/admin/library/ArtistDetail.vue b/front/src/views/admin/library/ArtistDetail.vue
index e6b4a127b34a5175ebff959f659a52e099609d9e..72e1bcb0848b0e4acbcb6964d1cb789bd59bdf16 100644
--- a/front/src/views/admin/library/ArtistDetail.vue
+++ b/front/src/views/admin/library/ArtistDetail.vue
@@ -117,6 +117,12 @@
                       {{ object.domain }}
                     </td>
                   </tr>
+                  <tr v-if="object.description">
+                    <td>
+                      <translate translate-context="'*/*/*/Noun">Description</translate>
+                    </td>
+                    <td v-html="object.description.html"></td>
+                  </tr>
                 </tbody>
               </table>
             </section>
diff --git a/front/src/views/admin/library/TrackDetail.vue b/front/src/views/admin/library/TrackDetail.vue
index 152d3390bf76034319f3ff2802c1a4c8fd30b6a8..d7f836d3a97d7c29a7ced121584e92cf69e9c427 100644
--- a/front/src/views/admin/library/TrackDetail.vue
+++ b/front/src/views/admin/library/TrackDetail.vue
@@ -181,6 +181,12 @@
                       {{ object.domain }}
                     </td>
                   </tr>
+                  <tr v-if="object.description">
+                    <td>
+                      <translate translate-context="'*/*/*/Noun">Description</translate>
+                    </td>
+                    <td v-html="object.description.html"></td>
+                  </tr>
                 </tbody>
               </table>
             </section>