diff --git a/api/funkwhale_api/common/migrations/0007_auto_20200116_1610.py b/api/funkwhale_api/common/migrations/0007_auto_20200116_1610.py
new file mode 100644
index 0000000000000000000000000000000000000000..5032d719ca66e15bc306bbde4dc69fa1249124ba
--- /dev/null
+++ b/api/funkwhale_api/common/migrations/0007_auto_20200116_1610.py
@@ -0,0 +1,18 @@
+# Generated by Django 2.2.9 on 2020-01-16 16:10
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('common', '0006_content'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='attachment',
+            name='url',
+            field=models.URLField(max_length=500, null=True),
+        ),
+    ]
diff --git a/api/funkwhale_api/common/models.py b/api/funkwhale_api/common/models.py
index 993b1cef19832976e12fc490cc8a52e2e972ebb3..1b70304eddad9b0ddd013a3cb9d3aecd9c5db0e6 100644
--- a/api/funkwhale_api/common/models.py
+++ b/api/funkwhale_api/common/models.py
@@ -175,7 +175,12 @@ def get_file_path(instance, filename):
 
 class AttachmentQuerySet(models.QuerySet):
     def attached(self, include=True):
-        related_fields = ["covered_album", "mutation_attachment"]
+        related_fields = [
+            "covered_album",
+            "mutation_attachment",
+            "covered_track",
+            "covered_artist",
+        ]
         query = None
         for field in related_fields:
             field_query = ~models.Q(**{field: None})
@@ -195,7 +200,7 @@ class AttachmentQuerySet(models.QuerySet):
 
 class Attachment(models.Model):
     # Remote URL where the attachment can be fetched
-    url = models.URLField(max_length=500, unique=True, null=True)
+    url = models.URLField(max_length=500, null=True)
     uuid = models.UUIDField(unique=True, db_index=True, default=uuid.uuid4)
     # Actor associated with the attachment
     actor = models.ForeignKey(
diff --git a/api/funkwhale_api/common/mutations.py b/api/funkwhale_api/common/mutations.py
index 586e86ec17c0865c48d91b88843d5fe0ac0d02bd..66a16e4b4ba33acf0e47052d51488f09ffb23461 100644
--- a/api/funkwhale_api/common/mutations.py
+++ b/api/funkwhale_api/common/mutations.py
@@ -85,8 +85,6 @@ class MutationSerializer(serializers.Serializer):
 
 
 class UpdateMutationSerializer(serializers.ModelSerializer, MutationSerializer):
-    serialized_relations = {}
-
     def __init__(self, *args, **kwargs):
         # we force partial mode, because update mutations are partial
         kwargs.setdefault("partial", True)
@@ -105,13 +103,14 @@ class UpdateMutationSerializer(serializers.ModelSerializer, MutationSerializer):
         return super().validate(validated_data)
 
     def db_serialize(self, validated_data):
+        serialized_relations = self.get_serialized_relations()
         data = {}
         # ensure model fields are serialized properly
         for key, value in list(validated_data.items()):
             if not isinstance(value, models.Model):
                 data[key] = value
                 continue
-            field = self.serialized_relations[key]
+            field = serialized_relations[key]
             data[key] = getattr(value, field)
         return data
 
@@ -120,7 +119,7 @@ class UpdateMutationSerializer(serializers.ModelSerializer, MutationSerializer):
         # we use our serialized_relations configuration
         # to ensure we store ids instead of model instances in our json
         # payload
-        for field, attr in self.serialized_relations.items():
+        for field, attr in self.get_serialized_relations().items():
             try:
                 obj = data[field]
             except KeyError:
@@ -139,10 +138,13 @@ class UpdateMutationSerializer(serializers.ModelSerializer, MutationSerializer):
         return get_update_previous_state(
             obj,
             *list(validated_data.keys()),
-            serialized_relations=self.serialized_relations,
+            serialized_relations=self.get_serialized_relations(),
             handlers=self.get_previous_state_handlers(),
         )
 
+    def get_serialized_relations(self):
+        return {}
+
     def get_previous_state_handlers(self):
         return {}
 
diff --git a/api/funkwhale_api/common/utils.py b/api/funkwhale_api/common/utils.py
index 9b230f411e2ce353bcb02bd7124fdba2eed5ec8c..9fb9f64e2928eb693646d57736824c02d22bf947 100644
--- a/api/funkwhale_api/common/utils.py
+++ b/api/funkwhale_api/common/utils.py
@@ -1,6 +1,8 @@
+from django.core.files.base import ContentFile
 from django.utils.deconstruct import deconstructible
 
 import bleach.sanitizer
+import logging
 import markdown
 import os
 import shutil
@@ -13,6 +15,8 @@ from django.conf import settings
 from django import urls
 from django.db import models, transaction
 
+logger = logging.getLogger(__name__)
+
 
 def rename_file(instance, field_name, new_name, allow_missing_file=False):
     field = getattr(instance, field_name)
@@ -306,3 +310,41 @@ def attach_content(obj, field, content_data):
     setattr(obj, field, content_obj)
     obj.save(update_fields=[field])
     return content_obj
+
+
+@transaction.atomic
+def attach_file(obj, field, file_data, fetch=False):
+    from . import models
+    from . import tasks
+
+    existing = getattr(obj, "{}_id".format(field))
+    if existing:
+        getattr(obj, field).delete()
+
+    if not file_data:
+        return
+
+    extensions = {"image/jpeg": "jpg", "image/png": "png", "image/gif": "gif"}
+    extension = extensions.get(file_data["mimetype"], "jpg")
+    attachment = models.Attachment(mimetype=file_data["mimetype"])
+
+    filename = "cover-{}.{}".format(obj.uuid, extension)
+    if "url" in file_data:
+        attachment.url = file_data["url"]
+    else:
+        f = ContentFile(file_data["content"])
+        attachment.file.save(filename, f, save=False)
+
+    if not attachment.file and fetch:
+        try:
+            tasks.fetch_remote_attachment(attachment, filename=filename, save=False)
+        except Exception as e:
+            logger.warn("Cannot download attachment at url %s: %s", attachment.url, e)
+            attachment = None
+
+    if attachment:
+        attachment.save()
+
+    setattr(obj, field, attachment)
+    obj.save(update_fields=[field])
+    return attachment
diff --git a/api/funkwhale_api/federation/serializers.py b/api/funkwhale_api/federation/serializers.py
index 2f593111f1f004d945b5a63887a48a6f26fcbef2..c55c99ed070e7917d57797ad5b7b5854b089f9cb 100644
--- a/api/funkwhale_api/federation/serializers.py
+++ b/api/funkwhale_api/federation/serializers.py
@@ -22,7 +22,7 @@ logger = logging.getLogger(__name__)
 
 
 class LinkSerializer(jsonld.JsonLdSerializer):
-    type = serializers.ChoiceField(choices=[contexts.AS.Link])
+    type = serializers.ChoiceField(choices=[contexts.AS.Link, contexts.AS.Image])
     href = serializers.URLField(max_length=500)
     mediaType = serializers.CharField()
 
@@ -817,6 +817,17 @@ def include_content(repr, content_obj):
     repr["mediaType"] = "text/html"
 
 
+def include_image(repr, attachment):
+    if attachment:
+        repr["image"] = {
+            "type": "Image",
+            "href": attachment.download_url_original,
+            "mediaType": attachment.mimetype or "image/jpeg",
+        }
+    else:
+        repr["image"] = None
+
+
 class TruncatedCharField(serializers.CharField):
     def __init__(self, *args, **kwargs):
         self.truncate_length = kwargs.pop("truncate_length")
@@ -877,6 +888,23 @@ class MusicEntitySerializer(jsonld.JsonLdSerializer):
         ]
 
     def validate_updated_data(self, instance, validated_data):
+        try:
+            attachment_cover = validated_data.pop("attachment_cover")
+        except KeyError:
+            return validated_data
+
+        if (
+            instance.attachment_cover
+            and instance.attachment_cover.url == attachment_cover["href"]
+        ):
+            # we already have the proper attachment
+            return validated_data
+        # create the attachment by hand so it can be attached as the cover
+        validated_data["attachment_cover"] = common_models.Attachment.objects.create(
+            mimetype=attachment_cover["mediaType"],
+            url=attachment_cover["href"],
+            actor=instance.attributed_to,
+        )
         return validated_data
 
     def validate(self, data):
@@ -890,15 +918,26 @@ class MusicEntitySerializer(jsonld.JsonLdSerializer):
 
 
 class ArtistSerializer(MusicEntitySerializer):
+    image = LinkSerializer(
+        allowed_mimetypes=["image/*"], allow_null=True, required=False
+    )
     updateable_fields = [
         ("name", "name"),
         ("musicbrainzId", "mbid"),
         ("attributedTo", "attributed_to"),
+        ("image", "attachment_cover"),
     ]
 
     class Meta:
         model = music_models.Artist
-        jsonld_mapping = MUSIC_ENTITY_JSONLD_MAPPING
+        jsonld_mapping = common_utils.concat_dicts(
+            MUSIC_ENTITY_JSONLD_MAPPING,
+            {
+                "released": jsonld.first_val(contexts.FW.released),
+                "artists": jsonld.first_attr(contexts.FW.artists, "@list"),
+                "image": jsonld.first_obj(contexts.AS.image),
+            },
+        )
 
     def to_representation(self, instance):
         d = {
@@ -913,6 +952,7 @@ class ArtistSerializer(MusicEntitySerializer):
             "tag": self.get_tags_repr(instance),
         }
         include_content(d, instance.description)
+        include_image(d, instance.attachment_cover)
         if self.context.get("include_ap_context", self.parent is None):
             d["@context"] = jsonld.get_default_context()
         return d
@@ -921,6 +961,7 @@ class ArtistSerializer(MusicEntitySerializer):
 class AlbumSerializer(MusicEntitySerializer):
     released = serializers.DateField(allow_null=True, required=False)
     artists = serializers.ListField(child=ArtistSerializer(), min_length=1)
+    # XXX: 1.0 rename to image
     cover = LinkSerializer(
         allowed_mimetypes=["image/*"], allow_null=True, required=False
     )
@@ -970,30 +1011,12 @@ class AlbumSerializer(MusicEntitySerializer):
                 "href": instance.attachment_cover.download_url_original,
                 "mediaType": instance.attachment_cover.mimetype or "image/jpeg",
             }
+            include_image(d, instance.attachment_cover)
+
         if self.context.get("include_ap_context", self.parent is None):
             d["@context"] = jsonld.get_default_context()
         return d
 
-    def validate_updated_data(self, instance, validated_data):
-        try:
-            attachment_cover = validated_data.pop("attachment_cover")
-        except KeyError:
-            return validated_data
-
-        if (
-            instance.attachment_cover
-            and instance.attachment_cover.url == attachment_cover["href"]
-        ):
-            # we already have the proper attachment
-            return validated_data
-        # create the attachment by hand so it can be attached as the album cover
-        validated_data["attachment_cover"] = common_models.Attachment.objects.create(
-            mimetype=attachment_cover["mediaType"],
-            url=attachment_cover["href"],
-            actor=instance.attributed_to,
-        )
-        return validated_data
-
 
 class TrackSerializer(MusicEntitySerializer):
     position = serializers.IntegerField(min_value=0, allow_null=True, required=False)
@@ -1002,6 +1025,9 @@ class TrackSerializer(MusicEntitySerializer):
     album = AlbumSerializer()
     license = serializers.URLField(allow_null=True, required=False)
     copyright = serializers.CharField(allow_null=True, required=False)
+    image = LinkSerializer(
+        allowed_mimetypes=["image/*"], allow_null=True, required=False
+    )
 
     updateable_fields = [
         ("name", "title"),
@@ -1011,6 +1037,7 @@ class TrackSerializer(MusicEntitySerializer):
         ("position", "position"),
         ("copyright", "copyright"),
         ("license", "license"),
+        ("image", "attachment_cover"),
     ]
 
     class Meta:
@@ -1024,6 +1051,7 @@ class TrackSerializer(MusicEntitySerializer):
                 "disc": jsonld.first_val(contexts.FW.disc),
                 "license": jsonld.first_id(contexts.FW.license),
                 "position": jsonld.first_val(contexts.FW.position),
+                "image": jsonld.first_obj(contexts.AS.image),
             },
         )
 
@@ -1054,6 +1082,7 @@ class TrackSerializer(MusicEntitySerializer):
             "tag": self.get_tags_repr(instance),
         }
         include_content(d, instance.description)
+        include_image(d, instance.attachment_cover)
         if self.context.get("include_ap_context", self.parent is None):
             d["@context"] = jsonld.get_default_context()
         return d
diff --git a/api/funkwhale_api/federation/views.py b/api/funkwhale_api/federation/views.py
index b732712dc6937282f14641d29179b87c00fd4b25..b57a8826197291f3bbd5b1d743ebe21fc3f81383 100644
--- a/api/funkwhale_api/federation/views.py
+++ b/api/funkwhale_api/federation/views.py
@@ -222,9 +222,12 @@ class MusicLibraryViewSet(
                     queryset=music_models.Track.objects.select_related(
                         "album__artist__attributed_to",
                         "artist__attributed_to",
+                        "artist__attachment_cover",
+                        "attachment_cover",
                         "album__attributed_to",
                         "attributed_to",
                         "album__attachment_cover",
+                        "album__artist__attachment_cover",
                         "description",
                     ).prefetch_related(
                         "tagged_items__tag",
@@ -283,6 +286,9 @@ class MusicUploadViewSet(
         "track__album__artist",
         "track__description",
         "track__album__attachment_cover",
+        "track__album__artist__attachment_cover",
+        "track__artist__attachment_cover",
+        "track__attachment_cover",
     )
     serializer_class = serializers.UploadSerializer
     lookup_field = "uuid"
@@ -303,7 +309,9 @@ class MusicArtistViewSet(
 ):
     authentication_classes = [authentication.SignatureAuthentication]
     renderer_classes = renderers.get_ap_renderers()
-    queryset = music_models.Artist.objects.local().select_related("description")
+    queryset = music_models.Artist.objects.local().select_related(
+        "description", "attachment_cover"
+    )
     serializer_class = serializers.ArtistSerializer
     lookup_field = "uuid"
 
@@ -314,7 +322,7 @@ class MusicAlbumViewSet(
     authentication_classes = [authentication.SignatureAuthentication]
     renderer_classes = renderers.get_ap_renderers()
     queryset = music_models.Album.objects.local().select_related(
-        "artist__description", "description"
+        "artist__description", "description", "artist__attachment_cover"
     )
     serializer_class = serializers.AlbumSerializer
     lookup_field = "uuid"
@@ -326,7 +334,14 @@ class MusicTrackViewSet(
     authentication_classes = [authentication.SignatureAuthentication]
     renderer_classes = renderers.get_ap_renderers()
     queryset = music_models.Track.objects.local().select_related(
-        "album__artist", "album__description", "artist__description", "description"
+        "album__artist",
+        "album__description",
+        "artist__description",
+        "description",
+        "attachment_cover",
+        "album__artist__attachment_cover",
+        "album__attachment_cover",
+        "artist__attachment_cover",
     )
     serializer_class = serializers.TrackSerializer
     lookup_field = "uuid"
diff --git a/api/funkwhale_api/manage/serializers.py b/api/funkwhale_api/manage/serializers.py
index 20c1d023861836669f452eff8b04ac84d07716fb..7adb9c86303def2879b91120704beb90e84d909d 100644
--- a/api/funkwhale_api/manage/serializers.py
+++ b/api/funkwhale_api/manage/serializers.py
@@ -390,6 +390,7 @@ class ManageArtistSerializer(
     tracks = ManageNestedTrackSerializer(many=True)
     attributed_to = ManageBaseActorSerializer()
     tags = serializers.SerializerMethodField()
+    cover = music_serializers.cover_field
 
     class Meta:
         model = music_models.Artist
@@ -398,6 +399,7 @@ class ManageArtistSerializer(
             "tracks",
             "attributed_to",
             "tags",
+            "cover",
         ]
 
     def get_tags(self, obj):
@@ -447,6 +449,7 @@ class ManageTrackSerializer(
     attributed_to = ManageBaseActorSerializer()
     uploads_count = serializers.SerializerMethodField()
     tags = serializers.SerializerMethodField()
+    cover = music_serializers.cover_field
 
     class Meta:
         model = music_models.Track
@@ -456,6 +459,7 @@ class ManageTrackSerializer(
             "attributed_to",
             "uploads_count",
             "tags",
+            "cover",
         ]
 
     def get_uploads_count(self, obj):
diff --git a/api/funkwhale_api/manage/views.py b/api/funkwhale_api/manage/views.py
index 1754a8970a3cf904c90ec235fad2b71ddf70dc48..24e09de47887c527142516e3c535f2fe929eade3 100644
--- a/api/funkwhale_api/manage/views.py
+++ b/api/funkwhale_api/manage/views.py
@@ -64,7 +64,7 @@ class ManageArtistViewSet(
     queryset = (
         music_models.Artist.objects.all()
         .order_by("-id")
-        .select_related("attributed_to")
+        .select_related("attributed_to", "attachment_cover",)
         .prefetch_related(
             "tracks",
             Prefetch(
@@ -164,7 +164,11 @@ class ManageTrackViewSet(
         music_models.Track.objects.all()
         .order_by("-id")
         .select_related(
-            "attributed_to", "artist", "album__artist", "album__attachment_cover"
+            "attributed_to",
+            "artist",
+            "album__artist",
+            "album__attachment_cover",
+            "attachment_cover",
         )
         .annotate(uploads_count=Coalesce(Subquery(uploads_subquery), 0))
         .prefetch_related(music_views.TAG_PREFETCH)
diff --git a/api/funkwhale_api/music/factories.py b/api/funkwhale_api/music/factories.py
index 07f0d9aa37d76c7b8ba5be467803d545a3fd3489..4247af3a29a9d3174448aa48a2b4870a80848251 100644
--- a/api/funkwhale_api/music/factories.py
+++ b/api/funkwhale_api/music/factories.py
@@ -64,6 +64,7 @@ class ArtistFactory(
     mbid = factory.Faker("uuid4")
     fid = factory.Faker("federation_url")
     playable = playable_factory("track__album__artist")
+    attachment_cover = factory.SubFactory(common_factories.AttachmentFactory)
 
     class Meta:
         model = "music.Artist"
@@ -111,6 +112,7 @@ class TrackFactory(
     album = factory.SubFactory(AlbumFactory)
     position = 1
     playable = playable_factory("track")
+    attachment_cover = factory.SubFactory(common_factories.AttachmentFactory)
 
     class Meta:
         model = "music.Track"
diff --git a/api/funkwhale_api/music/metadata.py b/api/funkwhale_api/music/metadata.py
index 78ca79168c302bf8b4776851f1d57226b410ca5b..ee24995e76d60a78dccb4266bf992d9aea365884 100644
--- a/api/funkwhale_api/music/metadata.py
+++ b/api/funkwhale_api/music/metadata.py
@@ -723,6 +723,7 @@ class TrackMetadataSerializer(serializers.Serializer):
                 continue
             if v in ["", None, []]:
                 validated_data.pop(field)
+        validated_data["album"]["cover_data"] = validated_data.pop("cover_data", None)
         return validated_data
 
 
diff --git a/api/funkwhale_api/music/migrations/0047_auto_20200116_1246.py b/api/funkwhale_api/music/migrations/0047_auto_20200116_1246.py
new file mode 100644
index 0000000000000000000000000000000000000000..9981cacfdf9b012f7601ac4aa41151d0fc40adeb
--- /dev/null
+++ b/api/funkwhale_api/music/migrations/0047_auto_20200116_1246.py
@@ -0,0 +1,30 @@
+# Generated by Django 2.2.9 on 2020-01-16 12:46
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('common', '0006_content'),
+        ('music', '0046_auto_20200113_1018'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='artist',
+            name='attachment_cover',
+            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='covered_artist', to='common.Attachment'),
+        ),
+        migrations.AddField(
+            model_name='track',
+            name='attachment_cover',
+            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='covered_track', to='common.Attachment'),
+        ),
+        migrations.AlterField(
+            model_name='album',
+            name='attachment_cover',
+            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='covered_album', to='common.Attachment'),
+        ),
+    ]
diff --git a/api/funkwhale_api/music/models.py b/api/funkwhale_api/music/models.py
index 9165e4658ed320c46d0348c709eaeb16c491fbb8..0d02236dd94f2822e7be1003108dcdc5b4c52047 100644
--- a/api/funkwhale_api/music/models.py
+++ b/api/funkwhale_api/music/models.py
@@ -230,6 +230,13 @@ class Artist(APIModelMixin):
     description = models.ForeignKey(
         "common.Content", null=True, blank=True, on_delete=models.SET_NULL
     )
+    attachment_cover = models.ForeignKey(
+        "common.Attachment",
+        null=True,
+        blank=True,
+        on_delete=models.SET_NULL,
+        related_name="covered_artist",
+    )
 
     api = musicbrainz.api.artists
     objects = ArtistQuerySet.as_manager()
@@ -248,6 +255,10 @@ class Artist(APIModelMixin):
         kwargs.update({"name": name})
         return cls.objects.get_or_create(name__iexact=name, defaults=kwargs)
 
+    @property
+    def cover(self):
+        return self.attachment_cover
+
 
 def import_artist(v):
     a = Artist.get_or_create_from_api(mbid=v[0]["artist"]["id"])[0]
@@ -358,44 +369,6 @@ class Album(APIModelMixin):
     }
     objects = AlbumQuerySet.as_manager()
 
-    def get_image(self, data=None):
-        from funkwhale_api.common import tasks as common_tasks
-
-        attachment = None
-        if data:
-            extensions = {"image/jpeg": "jpg", "image/png": "png", "image/gif": "gif"}
-            extension = extensions.get(data["mimetype"], "jpg")
-            attachment = common_models.Attachment(mimetype=data["mimetype"])
-            f = None
-            filename = "{}.{}".format(self.uuid, extension)
-            if data.get("content"):
-                # we have to cover itself
-                f = ContentFile(data["content"])
-                attachment.file.save(filename, f, save=False)
-            elif data.get("url"):
-                attachment.url = data.get("url")
-                # we can fetch from a url
-                try:
-                    common_tasks.fetch_remote_attachment(
-                        attachment, filename=filename, save=False
-                    )
-                except Exception as e:
-                    logger.warn(
-                        "Cannot download cover at url %s: %s", data.get("url"), e
-                    )
-                    return
-
-        elif self.mbid:
-            image_data = musicbrainz.api.images.get_front(str(self.mbid))
-            f = ContentFile(image_data)
-            attachment = common_models.Attachment(mimetype="image/jpeg")
-            attachment.file.save("{0}.jpg".format(self.mbid), f, save=False)
-        if attachment and attachment.file:
-            attachment.save()
-            self.attachment_cover = attachment
-            self.save(update_fields=["attachment_cover"])
-            return self.attachment_cover.file
-
     @property
     def cover(self):
         return self.attachment_cover
@@ -518,6 +491,13 @@ class Track(APIModelMixin):
     description = models.ForeignKey(
         "common.Content", null=True, blank=True, on_delete=models.SET_NULL
     )
+    attachment_cover = models.ForeignKey(
+        "common.Attachment",
+        null=True,
+        blank=True,
+        on_delete=models.SET_NULL,
+        related_name="covered_track",
+    )
 
     federation_namespace = "tracks"
     musicbrainz_model = "recording"
@@ -572,6 +552,10 @@ class Track(APIModelMixin):
         except AttributeError:
             return "{} - {}".format(self.artist.name, self.title)
 
+    @property
+    def cover(self):
+        return self.attachment_cover
+
     def get_activity_url(self):
         if self.mbid:
             return "https://musicbrainz.org/recording/{}".format(self.mbid)
diff --git a/api/funkwhale_api/music/mutations.py b/api/funkwhale_api/music/mutations.py
index d158857f06b98c58d50eb3aca1d2181d4e8b2b8b..c208d93a1fdd4be3b4b4d72ce144a030b5ddcb91 100644
--- a/api/funkwhale_api/music/mutations.py
+++ b/api/funkwhale_api/music/mutations.py
@@ -59,17 +59,66 @@ class DescriptionMutation(mutations.UpdateMutationSerializer):
         return r
 
 
+class CoverMutation(mutations.UpdateMutationSerializer):
+    cover = common_serializers.RelatedField(
+        "uuid", queryset=common_models.Attachment.objects.all().local(), serializer=None
+    )
+
+    def get_serialized_relations(self):
+        serialized_relations = super().get_serialized_relations()
+        serialized_relations["cover"] = "uuid"
+        return serialized_relations
+
+    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 update(self, instance, validated_data):
+        if "cover" in validated_data:
+            validated_data["attachment_cover"] = validated_data.pop("cover")
+        return super().update(instance, validated_data)
+
+    def mutation_post_init(self, mutation):
+        # link cover_attachment (if any) to mutation
+        if "cover" not in mutation.payload:
+            return
+        try:
+            attachment = common_models.Attachment.objects.get(
+                uuid=mutation.payload["cover"]
+            )
+        except common_models.Attachment.DoesNotExist:
+            return
+
+        common_models.MutationAttachment.objects.create(
+            attachment=attachment, mutation=mutation
+        )
+
+
 @mutations.registry.connect(
     "update",
     models.Track,
     perm_checkers={"suggest": can_suggest, "approve": can_approve},
 )
-class TrackMutationSerializer(TagMutation, DescriptionMutation):
-    serialized_relations = {"license": "code"}
-
+class TrackMutationSerializer(CoverMutation, TagMutation, DescriptionMutation):
     class Meta:
         model = models.Track
-        fields = ["license", "title", "position", "copyright", "tags", "description"]
+        fields = [
+            "license",
+            "title",
+            "position",
+            "copyright",
+            "tags",
+            "description",
+            "cover",
+        ]
+
+    def get_serialized_relations(self):
+        serialized_relations = super().get_serialized_relations()
+        serialized_relations["license"] = "code"
+        return serialized_relations
 
     def post_apply(self, obj, validated_data):
         routes.outbox.dispatch(
@@ -82,10 +131,10 @@ class TrackMutationSerializer(TagMutation, DescriptionMutation):
     models.Artist,
     perm_checkers={"suggest": can_suggest, "approve": can_approve},
 )
-class ArtistMutationSerializer(TagMutation, DescriptionMutation):
+class ArtistMutationSerializer(CoverMutation, TagMutation, DescriptionMutation):
     class Meta:
         model = models.Artist
-        fields = ["name", "tags", "description"]
+        fields = ["name", "tags", "description", "cover"]
 
     def post_apply(self, obj, validated_data):
         routes.outbox.dispatch(
@@ -98,45 +147,12 @@ class ArtistMutationSerializer(TagMutation, DescriptionMutation):
     models.Album,
     perm_checkers={"suggest": can_suggest, "approve": can_approve},
 )
-class AlbumMutationSerializer(TagMutation, DescriptionMutation):
-    cover = common_serializers.RelatedField(
-        "uuid", queryset=common_models.Attachment.objects.all().local(), serializer=None
-    )
-
-    serialized_relations = {"cover": "uuid"}
-
+class AlbumMutationSerializer(CoverMutation, TagMutation, DescriptionMutation):
     class Meta:
         model = models.Album
         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(
             {"type": "Update", "object": {"type": "Album"}}, context={"album": obj}
         )
-
-    def update(self, instance, validated_data):
-        if "cover" in validated_data:
-            validated_data["attachment_cover"] = validated_data.pop("cover")
-        return super().update(instance, validated_data)
-
-    def mutation_post_init(self, mutation):
-        # link cover_attachment (if any) to mutation
-        if "cover" not in mutation.payload:
-            return
-        try:
-            attachment = common_models.Attachment.objects.get(
-                uuid=mutation.payload["cover"]
-            )
-        except common_models.Attachment.DoesNotExist:
-            return
-
-        common_models.MutationAttachment.objects.create(
-            attachment=attachment, mutation=mutation
-        )
diff --git a/api/funkwhale_api/music/serializers.py b/api/funkwhale_api/music/serializers.py
index dcaeaadd0e5d464becba67535c22e464c0a21b3c..34025f297626b88771ad133e8edbbc54d0f707d6 100644
--- a/api/funkwhale_api/music/serializers.py
+++ b/api/funkwhale_api/music/serializers.py
@@ -121,6 +121,7 @@ class ArtistWithAlbumsSerializer(OptionalDescriptionMixin, serializers.Serialize
     name = serializers.CharField()
     creation_date = serializers.DateTimeField()
     is_local = serializers.BooleanField()
+    cover = cover_field
 
     def get_tags(self, obj):
         tagged_items = getattr(obj, "_prefetched_tagged_items", [])
@@ -266,7 +267,7 @@ class TrackSerializer(OptionalDescriptionMixin, serializers.Serializer):
     disc_number = serializers.IntegerField()
     copyright = serializers.CharField()
     license = serializers.SerializerMethodField()
-
+    cover = cover_field
     get_attributed_to = serialize_attributed_to
 
     def get_artist(self, o):
diff --git a/api/funkwhale_api/music/tasks.py b/api/funkwhale_api/music/tasks.py
index e6b63207249af27472ffd68210d2d840fe13ad31..3e95e5325cd1a9e75991ec4a6a5152f6c49121a4 100644
--- a/api/funkwhale_api/music/tasks.py
+++ b/api/funkwhale_api/music/tasks.py
@@ -11,6 +11,7 @@ from django.dispatch import receiver
 from musicbrainzngs import ResponseError
 from requests.exceptions import RequestException
 
+from funkwhale_api import musicbrainz
 from funkwhale_api.common import channels, preferences
 from funkwhale_api.common import utils as common_utils
 from funkwhale_api.federation import routes
@@ -28,33 +29,34 @@ from . import signals
 logger = logging.getLogger(__name__)
 
 
-def update_album_cover(
-    album, source=None, cover_data=None, musicbrainz=True, replace=False
-):
+def populate_album_cover(album, source=None, replace=False):
     if album.attachment_cover and not replace:
         return
-    if cover_data:
-        return album.get_image(data=cover_data)
-
     if source and source.startswith("file://"):
         # let's look for a cover in the same directory
         path = os.path.dirname(source.replace("file://", "", 1))
         logger.info("[Album %s] scanning covers from %s", album.pk, path)
         cover = get_cover_from_fs(path)
-        if cover:
-            return album.get_image(data=cover)
-    if musicbrainz and album.mbid:
+        return common_utils.attach_file(album, "attachment_cover", cover)
+    if album.mbid:
+        logger.info(
+            "[Album %s] Fetching cover from musicbrainz release %s",
+            album.pk,
+            str(album.mbid),
+        )
         try:
-            logger.info(
-                "[Album %s] Fetching cover from musicbrainz release %s",
-                album.pk,
-                str(album.mbid),
-            )
-            return album.get_image()
+            image_data = musicbrainz.api.images.get_front(str(album.mbid))
         except ResponseError as exc:
             logger.warning(
                 "[Album %s] cannot fetch cover from musicbrainz: %s", album.pk, str(exc)
             )
+        else:
+            return common_utils.attach_file(
+                album,
+                "attachment_cover",
+                {"content": image_data, "mimetype": "image/jpeg"},
+                fetch=True,
+            )
 
 
 IMAGE_TYPES = [("jpg", "image/jpeg"), ("jpeg", "image/jpeg"), ("png", "image/png")]
@@ -274,10 +276,8 @@ def process_upload(upload, update_denormalization=True):
 
     # update album cover, if needed
     if not track.album.attachment_cover:
-        update_album_cover(
-            track.album,
-            source=final_metadata.get("upload_source"),
-            cover_data=final_metadata.get("cover_data"),
+        populate_album_cover(
+            track.album, source=final_metadata.get("upload_source"),
         )
 
     broadcast = getter(
@@ -299,6 +299,12 @@ def process_upload(upload, update_denormalization=True):
         )
 
 
+def get_cover(obj, field):
+    cover = obj.get(field)
+    if cover:
+        return {"mimetype": cover["mediaType"], "url": cover["href"]}
+
+
 def federation_audio_track_to_metadata(payload, references):
     """
     Given a valid payload as returned by federation.serializers.TrackSerializer.validated_data,
@@ -315,6 +321,7 @@ def federation_audio_track_to_metadata(payload, references):
         "mbid": str(payload.get("musicbrainzId"))
         if payload.get("musicbrainzId")
         else None,
+        "cover_data": get_cover(payload, "image"),
         "album": {
             "title": payload["album"]["name"],
             "fdate": payload["album"]["published"],
@@ -324,6 +331,7 @@ def federation_audio_track_to_metadata(payload, references):
             "mbid": str(payload["album"]["musicbrainzId"])
             if payload["album"].get("musicbrainzId")
             else None,
+            "cover_data": get_cover(payload["album"], "cover"),
             "release_date": payload["album"].get("released"),
             "tags": [t["name"] for t in payload["album"].get("tags", []) or []],
             "artists": [
@@ -331,6 +339,7 @@ def federation_audio_track_to_metadata(payload, references):
                     "fid": a["id"],
                     "name": a["name"],
                     "fdate": a["published"],
+                    "cover_data": get_cover(a, "image"),
                     "description": a.get("description"),
                     "attributed_to": references.get(a.get("attributedTo")),
                     "mbid": str(a["musicbrainzId"]) if a.get("musicbrainzId") else None,
@@ -348,6 +357,7 @@ def federation_audio_track_to_metadata(payload, references):
                 "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 []],
+                "cover_data": get_cover(a, "image"),
             }
             for a in payload["artists"]
         ],
@@ -356,9 +366,6 @@ def federation_audio_track_to_metadata(payload, references):
         "fdate": payload["published"],
         "tags": [t["name"] for t in payload.get("tags", []) or []],
     }
-    cover = payload["album"].get("cover")
-    if cover:
-        new_data["cover_data"] = {"mimetype": cover["mediaType"], "url": cover["href"]}
     return new_data
 
 
@@ -427,11 +434,7 @@ def get_track_from_import_metadata(
 ):
     track = _get_track(data, attributed_to=attributed_to, **forced_values)
     if update_cover and track and not track.album.attachment_cover:
-        update_album_cover(
-            track.album,
-            source=data.get("upload_source"),
-            cover_data=data.get("cover_data"),
-        )
+        populate_album_cover(track.album, source=data.get("upload_source"))
     return track
 
 
@@ -513,6 +516,9 @@ def _get_track(data, attributed_to=None, **forced_values):
             common_utils.attach_content(
                 artist, "description", artist_data.get("description")
             )
+            common_utils.attach_file(
+                artist, "attachment_cover", artist_data.get("cover_data")
+            )
 
     if "album" in forced_values:
         album = forced_values["album"]
@@ -550,6 +556,11 @@ def _get_track(data, attributed_to=None, **forced_values):
                 common_utils.attach_content(
                     album_artist, "description", album_artist_data.get("description")
                 )
+                common_utils.attach_file(
+                    album_artist,
+                    "attachment_cover",
+                    album_artist_data.get("cover_data"),
+                )
 
         # get / create album
         album_data = data["album"]
@@ -583,6 +594,9 @@ def _get_track(data, attributed_to=None, **forced_values):
             common_utils.attach_content(
                 album, "description", album_data.get("description")
             )
+            common_utils.attach_file(
+                album, "attachment_cover", album_data.get("cover_data")
+            )
 
     # get / create track
     track_title = (
@@ -643,6 +657,7 @@ def _get_track(data, attributed_to=None, **forced_values):
         )
         tags_models.add_tags(track, *tags)
         common_utils.attach_content(track, "description", data.get("description"))
+        common_utils.attach_file(track, "attachment_cover", data.get("cover_data"))
 
     return track
 
diff --git a/api/funkwhale_api/music/views.py b/api/funkwhale_api/music/views.py
index f7fb6e653b80576bc82d71b898e688b23dc659ab..28babcc9629bfc6201fda254a8e6e71d55235e44 100644
--- a/api/funkwhale_api/music/views.py
+++ b/api/funkwhale_api/music/views.py
@@ -113,7 +113,7 @@ class ArtistViewSet(
 ):
     queryset = (
         models.Artist.objects.all()
-        .prefetch_related("attributed_to")
+        .prefetch_related("attributed_to", "attachment_cover")
         .prefetch_related(
             Prefetch(
                 "tracks",
@@ -295,7 +295,7 @@ class TrackViewSet(
     queryset = (
         models.Track.objects.all()
         .for_nested_serialization()
-        .prefetch_related("attributed_to")
+        .prefetch_related("attributed_to", "attachment_cover")
         .order_by("-creation_date")
     )
     serializer_class = serializers.TrackSerializer
@@ -558,7 +558,12 @@ class UploadViewSet(
     queryset = (
         models.Upload.objects.all()
         .order_by("-creation_date")
-        .prefetch_related("library", "track__artist", "track__album__artist")
+        .prefetch_related(
+            "library",
+            "track__artist",
+            "track__album__artist",
+            "track__attachment_cover",
+        )
     )
     serializer_class = serializers.UploadForOwnerSerializer
     permission_classes = [
diff --git a/api/requirements/test.txt b/api/requirements/test.txt
index 051a117dfe19543c3209e697e1b1b5c3f8ef74f9..508b1c689b83c3495c4b6a6a36a44ccaa880a6b7 100644
--- a/api/requirements/test.txt
+++ b/api/requirements/test.txt
@@ -1,7 +1,7 @@
 # Test dependencies go here.
 
 flake8
-pytest>=5
+pytest>=5,<5.3.3
 pytest-django>=3.5.1
 pytest-mock
 pytest-sugar
diff --git a/api/tests/common/test_models.py b/api/tests/common/test_models.py
index 9fdde0378b1471babc5805cd00e42f75226fc358..f60ee3ef5c7eced441f5e5a4ba14551ed8ce03be 100644
--- a/api/tests/common/test_models.py
+++ b/api/tests/common/test_models.py
@@ -64,7 +64,7 @@ def test_attachment(factories, now):
 @pytest.mark.parametrize("args, expected", [([], [0]), ([True], [0]), ([False], [1])])
 def test_attachment_queryset_attached(args, expected, factories, queryset_equal_list):
     attachments = [
-        factories["music.Album"]().attachment_cover,
+        factories["music.Album"](artist__attachment_cover=None).attachment_cover,
         factories["common.Attachment"](),
     ]
 
diff --git a/api/tests/common/test_mutations.py b/api/tests/common/test_mutations.py
index 877128fac62da638744e0855b925fc816d8b0f95..d006f83f7c524ee22c9466d5cacc7a0b34bef3ff 100644
--- a/api/tests/common/test_mutations.py
+++ b/api/tests/common/test_mutations.py
@@ -63,12 +63,13 @@ def test_db_serialize_update_mutation(factories, mutations_registry, mocker):
     user = factories["users.User"](email="hello@test.email", with_actor=True)
 
     class S(mutations.UpdateMutationSerializer):
-        serialized_relations = {"actor": "full_username"}
-
         class Meta:
             model = user.__class__
             fields = ["actor"]
 
+        def get_serialized_relations(self):
+            return {"actor": "full_username"}
+
     expected = {"actor": user.actor.full_username}
     assert S().db_serialize({"actor": user.actor}) == expected
 
diff --git a/api/tests/common/test_utils.py b/api/tests/common/test_utils.py
index def5434a4b8c1ef3daf07bf91929b61a8216ef8a..478a9e8cc0b8e8f54213a690a87a27528ac07d22 100644
--- a/api/tests/common/test_utils.py
+++ b/api/tests/common/test_utils.py
@@ -1,3 +1,4 @@
+import io
 import pytest
 
 from funkwhale_api.common import utils
@@ -124,3 +125,51 @@ def test_join_url(start, end, expected):
 def test_render_html(text, content_type, expected):
     result = utils.render_html(text, content_type)
     assert result == expected
+
+
+def test_attach_file_url(factories):
+    album = factories["music.Album"]()
+    existing_attachment = album.attachment_cover
+    assert existing_attachment is not None
+
+    data = {"mimetype": "image/jpeg", "url": "https://example.com/test.jpg"}
+    new_attachment = utils.attach_file(album, "attachment_cover", data)
+
+    album.refresh_from_db()
+
+    with pytest.raises(existing_attachment.DoesNotExist):
+        existing_attachment.refresh_from_db()
+
+    assert album.attachment_cover == new_attachment
+    assert not new_attachment.file
+    assert new_attachment.url == data["url"]
+    assert new_attachment.mimetype == data["mimetype"]
+
+
+def test_attach_file_url_fetch(factories, r_mock):
+    album = factories["music.Album"]()
+
+    data = {"mimetype": "image/jpeg", "url": "https://example.com/test.jpg"}
+    r_mock.get(data["url"], body=io.BytesIO(b"content"))
+    new_attachment = utils.attach_file(album, "attachment_cover", data, fetch=True)
+
+    album.refresh_from_db()
+
+    assert album.attachment_cover == new_attachment
+    assert new_attachment.file.read() == b"content"
+    assert new_attachment.url == data["url"]
+    assert new_attachment.mimetype == data["mimetype"]
+
+
+def test_attach_file_content(factories, r_mock):
+    album = factories["music.Album"]()
+
+    data = {"mimetype": "image/jpeg", "content": b"content"}
+    new_attachment = utils.attach_file(album, "attachment_cover", data)
+
+    album.refresh_from_db()
+
+    assert album.attachment_cover == new_attachment
+    assert new_attachment.file.read() == b"content"
+    assert new_attachment.url is None
+    assert new_attachment.mimetype == data["mimetype"]
diff --git a/api/tests/federation/test_routes.py b/api/tests/federation/test_routes.py
index 995cb1e446e1b6274f87b435a391e0406b46b5ef..83f10e5cd402c1d55076a4652a817c96b0705cce 100644
--- a/api/tests/federation/test_routes.py
+++ b/api/tests/federation/test_routes.py
@@ -540,7 +540,7 @@ def test_inbox_update_artist(factories, mocker):
         "funkwhale_api.music.tasks.update_library_entity"
     )
     activity = factories["federation.Activity"]()
-    obj = factories["music.Artist"](attributed=True)
+    obj = factories["music.Artist"](attributed=True, attachment_cover=None)
     actor = obj.attributed_to
     data = serializers.ArtistSerializer(obj).data
     data["name"] = "New name"
@@ -602,7 +602,7 @@ def test_inbox_update_track(factories, mocker):
         "funkwhale_api.music.tasks.update_library_entity"
     )
     activity = factories["federation.Activity"]()
-    obj = factories["music.Track"](attributed=True)
+    obj = factories["music.Track"](attributed=True, attachment_cover=None)
     actor = obj.attributed_to
     data = serializers.TrackSerializer(obj).data
     data["name"] = "New title"
diff --git a/api/tests/federation/test_serializers.py b/api/tests/federation/test_serializers.py
index e158b604186ef02e16fde04810b0a99c3c7b8437..4f31dcbdfb4fc714bae53d584b61165563d56bba 100644
--- a/api/tests/federation/test_serializers.py
+++ b/api/tests/federation/test_serializers.py
@@ -575,6 +575,11 @@ def test_activity_pub_artist_serializer_to_ap(factories):
         "attributedTo": artist.attributed_to.fid,
         "mediaType": "text/html",
         "content": common_utils.render_html(content.text, content.content_type),
+        "image": {
+            "type": "Image",
+            "mediaType": "image/jpeg",
+            "href": utils.full_url(artist.attachment_cover.file.url),
+        },
         "tag": [
             {"type": "Hashtag", "name": "#Punk"},
             {"type": "Hashtag", "name": "#Rock"},
@@ -601,6 +606,11 @@ def test_activity_pub_album_serializer_to_ap(factories):
             "mediaType": "image/jpeg",
             "href": utils.full_url(album.attachment_cover.file.url),
         },
+        "image": {
+            "type": "Image",
+            "mediaType": "image/jpeg",
+            "href": utils.full_url(album.attachment_cover.file.url),
+        },
         "musicbrainzId": album.mbid,
         "published": album.creation_date.isoformat(),
         "released": album.release_date.isoformat(),
@@ -622,6 +632,44 @@ def test_activity_pub_album_serializer_to_ap(factories):
     assert serializer.data == expected
 
 
+def test_activity_pub_artist_serializer_from_ap_update(factories, faker):
+    artist = factories["music.Artist"](attributed=True)
+    payload = {
+        "@context": jsonld.get_default_context(),
+        "type": "Artist",
+        "id": artist.fid,
+        "name": faker.sentence(),
+        "musicbrainzId": faker.uuid4(),
+        "published": artist.creation_date.isoformat(),
+        "attributedTo": artist.attributed_to.fid,
+        "mediaType": "text/html",
+        "content": common_utils.render_html(faker.sentence(), "text/html"),
+        "image": {"type": "Image", "mediaType": "image/jpeg", "href": faker.url()},
+        "tag": [
+            {"type": "Hashtag", "name": "#Punk"},
+            {"type": "Hashtag", "name": "#Rock"},
+        ],
+    }
+
+    serializer = serializers.ArtistSerializer(artist, data=payload)
+    assert serializer.is_valid(raise_exception=True) is True
+
+    serializer.save()
+
+    artist.refresh_from_db()
+
+    assert artist.name == payload["name"]
+    assert str(artist.mbid) == payload["musicbrainzId"]
+    assert artist.attachment_cover.url == payload["image"]["href"]
+    assert artist.attachment_cover.mimetype == payload["image"]["mediaType"]
+    assert artist.description.text == payload["content"]
+    assert artist.description.content_type == "text/html"
+    assert sorted(artist.tagged_items.values_list("tag__name", flat=True)) == [
+        "Punk",
+        "Rock",
+    ]
+
+
 def test_activity_pub_album_serializer_from_ap_update(factories, faker):
     album = factories["music.Album"](attributed=True)
     released = faker.date_object()
@@ -699,6 +747,11 @@ def test_activity_pub_track_serializer_to_ap(factories):
             {"type": "Hashtag", "name": "#Punk"},
             {"type": "Hashtag", "name": "#Rock"},
         ],
+        "image": {
+            "type": "Image",
+            "mediaType": "image/jpeg",
+            "href": utils.full_url(track.attachment_cover.file.url),
+        },
     }
     serializer = serializers.TrackSerializer(track)
 
@@ -726,6 +779,11 @@ def test_activity_pub_track_serializer_from_ap(factories, r_mock, mocker):
         "disc": 1,
         "content": "Hello there",
         "attributedTo": track_attributed_to.fid,
+        "image": {
+            "type": "Link",
+            "href": "https://cover.image/track.png",
+            "mediaType": "image/png",
+        },
         "album": {
             "type": "Album",
             "id": "http://hello.album",
@@ -753,6 +811,11 @@ def test_activity_pub_track_serializer_from_ap(factories, r_mock, mocker):
                     "published": published.isoformat(),
                     "attributedTo": album_artist_attributed_to.fid,
                     "tag": [{"type": "Hashtag", "name": "AlbumArtistTag"}],
+                    "image": {
+                        "type": "Link",
+                        "href": "https://cover.image/album-artist.png",
+                        "mediaType": "image/png",
+                    },
                 }
             ],
         },
@@ -767,6 +830,11 @@ def test_activity_pub_track_serializer_from_ap(factories, r_mock, mocker):
                 "attributedTo": artist_attributed_to.fid,
                 "published": published.isoformat(),
                 "tag": [{"type": "Hashtag", "name": "ArtistTag"}],
+                "image": {
+                    "type": "Link",
+                    "href": "https://cover.image/artist.png",
+                    "mediaType": "image/png",
+                },
             }
         ],
         "tag": [
@@ -774,7 +842,6 @@ def test_activity_pub_track_serializer_from_ap(factories, r_mock, mocker):
             {"type": "Hashtag", "name": "World"},
         ],
     }
-    r_mock.get(data["album"]["cover"]["href"], body=io.BytesIO(b"coucou"))
     serializer = serializers.TrackSerializer(data=data, context={"activity": activity})
     assert serializer.is_valid(raise_exception=True)
 
@@ -793,10 +860,12 @@ def test_activity_pub_track_serializer_from_ap(factories, r_mock, mocker):
     assert str(track.mbid) == data["musicbrainzId"]
     assert track.description.text == data["content"]
     assert track.description.content_type == "text/html"
+    assert track.attachment_cover.url == data["image"]["href"]
+    assert track.attachment_cover.mimetype == data["image"]["mediaType"]
 
     assert album.from_activity == activity
-    assert album.attachment_cover.file.read() == b"coucou"
-    assert album.attachment_cover.file.path.endswith(".png")
+    assert album.attachment_cover.url == data["album"]["cover"]["href"]
+    assert album.attachment_cover.mimetype == data["album"]["cover"]["mediaType"]
     assert album.title == data["album"]["name"]
     assert album.fid == data["album"]["id"]
     assert str(album.mbid) == data["album"]["musicbrainzId"]
@@ -814,6 +883,8 @@ def test_activity_pub_track_serializer_from_ap(factories, r_mock, mocker):
     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 artist.attachment_cover.url == data["artists"][0]["image"]["href"]
+    assert artist.attachment_cover.mimetype == data["artists"][0]["image"]["mediaType"]
 
     assert album_artist.from_activity == activity
     assert album_artist.name == data["album"]["artists"][0]["name"]
@@ -826,6 +897,14 @@ def test_activity_pub_track_serializer_from_ap(factories, r_mock, mocker):
         album_artist.description.content_type
         == data["album"]["artists"][0]["mediaType"]
     )
+    assert (
+        album_artist.attachment_cover.url
+        == data["album"]["artists"][0]["image"]["href"]
+    )
+    assert (
+        album_artist.attachment_cover.mimetype
+        == data["album"]["artists"][0]["image"]["mediaType"]
+    )
 
     add_tags.assert_any_call(track, *["Hello", "World"])
     add_tags.assert_any_call(album, *["AlbumTag"])
@@ -833,7 +912,7 @@ def test_activity_pub_track_serializer_from_ap(factories, r_mock, mocker):
     add_tags.assert_any_call(artist, *["ArtistTag"])
 
 
-def test_activity_pub_track_serializer_from_ap_update(factories, r_mock, mocker):
+def test_activity_pub_track_serializer_from_ap_update(factories, r_mock, mocker, faker):
     set_tags = mocker.patch("funkwhale_api.tags.models.set_tags")
     content = factories["common.Content"]()
     track_attributed_to = factories["federation.Actor"]()
@@ -853,6 +932,7 @@ def test_activity_pub_track_serializer_from_ap_update(factories, r_mock, mocker)
         "attributedTo": track_attributed_to.fid,
         "album": serializers.AlbumSerializer(track.album).data,
         "artists": [serializers.ArtistSerializer(track.artist).data],
+        "image": {"type": "Link", "mediaType": "image/jpeg", "href": faker.url()},
         "tag": [
             {"type": "Hashtag", "name": "#Hello"},
             # Ensure we can handle tags without a leading #
@@ -873,6 +953,8 @@ def test_activity_pub_track_serializer_from_ap_update(factories, r_mock, mocker)
     assert track.description.content_type == "text/html"
     assert track.description.text == "hello there"
     assert str(track.mbid) == data["musicbrainzId"]
+    assert track.attachment_cover.url == data["image"]["href"]
+    assert track.attachment_cover.mimetype == data["image"]["mediaType"]
 
     set_tags.assert_called_once_with(track, *["Hello", "World"])
 
diff --git a/api/tests/manage/test_serializers.py b/api/tests/manage/test_serializers.py
index a80015ea9643cc5d3c77770049c351a89603b228..412a7a58c6e3ec9e4fd1cd5131699ee7d8684949 100644
--- a/api/tests/manage/test_serializers.py
+++ b/api/tests/manage/test_serializers.py
@@ -303,6 +303,7 @@ def test_manage_artist_serializer(factories, now, to_api_date):
             artist.attributed_to
         ).data,
         "tags": [],
+        "cover": common_serializers.AttachmentSerializer(artist.attachment_cover).data,
     }
     s = serializers.ManageArtistSerializer(artist)
 
@@ -412,6 +413,7 @@ def test_manage_track_serializer(factories, now, to_api_date):
         ).data,
         "uploads_count": 44,
         "tags": [],
+        "cover": common_serializers.AttachmentSerializer(track.attachment_cover).data,
     }
     s = serializers.ManageTrackSerializer(track)
 
diff --git a/api/tests/music/test_metadata.py b/api/tests/music/test_metadata.py
index d842d33183780e35e2e8f2668dced3d8f19ec31c..485502ac739975715eb458237afcca219baaa5b2 100644
--- a/api/tests/music/test_metadata.py
+++ b/api/tests/music/test_metadata.py
@@ -440,7 +440,7 @@ def test_track_metadata_serializer(path, expected, mocker):
     path = os.path.join(DATA_DIR, path)
     data = metadata.Metadata(path)
     get_picture = mocker.patch.object(data, "get_picture")
-    expected["cover_data"] = get_picture.return_value
+    expected["album"]["cover_data"] = get_picture.return_value
 
     serializer = metadata.TrackMetadataSerializer(data=data)
     assert serializer.is_valid(raise_exception=True) is True
@@ -566,13 +566,13 @@ def test_fake_metadata_with_serializer():
                     "mbid": uuid.UUID("5b4d7d2d-36df-4b38-95e3-a964234f520f"),
                 },
             ],
+            "cover_data": None,
         },
         "position": 1,
         "disc_number": 1,
         "mbid": uuid.UUID("bd21ac48-46d8-4e78-925f-d9cc2a294656"),
         "license": "Dummy license: http://creativecommons.org/licenses/by-sa/4.0/",
         "copyright": "Someone",
-        "cover_data": None,
     }
     serializer = metadata.TrackMetadataSerializer(data=metadata.FakeMetadata(data))
     assert serializer.is_valid(raise_exception=True) is True
@@ -594,8 +594,8 @@ def test_serializer_album_artist_missing():
             "mbid": None,
             "release_date": None,
             "artists": [],
+            "cover_data": None,
         },
-        "cover_data": None,
     }
     serializer = metadata.TrackMetadataSerializer(data=metadata.FakeMetadata(data))
     assert serializer.is_valid(raise_exception=True) is True
@@ -622,8 +622,8 @@ def test_serializer_album_default_title_when_missing_or_empty(data):
             "mbid": None,
             "release_date": None,
             "artists": [],
+            "cover_data": None,
         },
-        "cover_data": None,
     }
     serializer = metadata.TrackMetadataSerializer(data=metadata.FakeMetadata(data))
     assert serializer.is_valid(raise_exception=True) is True
@@ -649,8 +649,8 @@ def test_serializer_empty_fields(field_name):
             "mbid": None,
             "release_date": None,
             "artists": [],
+            "cover_data": None,
         },
-        "cover_data": None,
     }
     serializer = metadata.TrackMetadataSerializer(data=metadata.FakeMetadata(data))
     assert serializer.is_valid(raise_exception=True) is True
@@ -701,8 +701,8 @@ def test_acquire_tags_from_genre(genre, expected_tags):
             "mbid": None,
             "release_date": None,
             "artists": [],
+            "cover_data": None,
         },
-        "cover_data": None,
     }
     if expected_tags:
         expected["tags"] = expected_tags
diff --git a/api/tests/music/test_models.py b/api/tests/music/test_models.py
index d8022c9646f7419e4c39ebe4f926b6dd7122c0ac..74d5e9604b0fc8f29badf517c4cd1dea2cb90826 100644
--- a/api/tests/music/test_models.py
+++ b/api/tests/music/test_models.py
@@ -187,14 +187,6 @@ def test_track_get_file_size_in_place(factories):
     assert upload.get_file_size() == 297745
 
 
-def test_album_get_image_content(factories):
-    album = factories["music.Album"]()
-    album.get_image(data={"content": b"test", "mimetype": "image/jpeg"})
-    album.refresh_from_db()
-
-    assert album.attachment_cover.file.read() == b"test"
-
-
 def test_library(factories):
     now = timezone.now()
     actor = factories["federation.Actor"]()
diff --git a/api/tests/music/test_music.py b/api/tests/music/test_music.py
index 0709ab3404c01d894c638db2bc41261692d1e934..79402b47c0eb57921a79607c23cba96484f411b2 100644
--- a/api/tests/music/test_music.py
+++ b/api/tests/music/test_music.py
@@ -121,23 +121,3 @@ def test_can_get_or_create_track_from_api(artists, albums, tracks, mocker, db):
     track2, created = models.Track.get_or_create_from_api(mbid=data["id"])
     assert not created
     assert track == track2
-
-
-def test_can_download_image_file_for_album(binary_cover, mocker, factories):
-    mocker.patch(
-        "funkwhale_api.musicbrainz.api.images.get_front", return_value=binary_cover
-    )
-    # client._api.get_image_front('55ea4f82-b42b-423e-a0e5-290ccdf443ed')
-    album = factories["music.Album"](mbid="55ea4f82-b42b-423e-a0e5-290ccdf443ed")
-    album.get_image()
-    album.save()
-
-    assert album.attachment_cover.file.read() == binary_cover
-
-
-def test_album_get_image_doesnt_crash_with_empty_data(mocker, factories):
-    album = factories["music.Album"](mbid=None, attachment_cover=None)
-    assert (
-        album.get_image(data={"content": "", "url": "", "mimetype": "image/png"})
-        is None
-    )
diff --git a/api/tests/music/test_mutations.py b/api/tests/music/test_mutations.py
index eff6925d39ad0d9c3fc8b8b87ec3f9c988b9e44e..a0f6847697380e3c745c8491549ab24bfdaea0c0 100644
--- a/api/tests/music/test_mutations.py
+++ b/api/tests/music/test_mutations.py
@@ -179,9 +179,10 @@ def test_perm_checkers_can_approve(
     assert mutations.can_approve(obj, actor=actor) is expected
 
 
-def test_mutation_set_attachment_cover(factories, now, mocker):
+@pytest.mark.parametrize("factory_name", ["music.Artist", "music.Track", "music.Album"])
+def test_mutation_set_attachment_cover(factory_name, factories, now, mocker):
     new_attachment = factories["common.Attachment"](actor__local=True)
-    obj = factories["music.Album"]()
+    obj = factories[factory_name]()
     old_attachment = obj.attachment_cover
     mutation = factories["common.Mutation"](
         type="update", target=obj, payload={"cover": new_attachment.uuid}
diff --git a/api/tests/music/test_serializers.py b/api/tests/music/test_serializers.py
index a35180cefcb7e5c584d28a8d1a0e57bdcdbc3fcf..156abe33b9c384ce667b981bcddd9b70fa76d526 100644
--- a/api/tests/music/test_serializers.py
+++ b/api/tests/music/test_serializers.py
@@ -71,6 +71,7 @@ def test_artist_with_albums_serializer(factories, to_api_date):
         "tags": [],
         "attributed_to": federation_serializers.APIActorSerializer(actor).data,
         "tracks_count": 42,
+        "cover": common_serializers.AttachmentSerializer(artist.attachment_cover).data,
     }
     serializer = serializers.ArtistWithAlbumsSerializer(artist)
     assert serializer.data == expected
@@ -217,6 +218,7 @@ def test_track_serializer(factories, to_api_date):
         "is_local": upload.track.is_local,
         "tags": [],
         "attributed_to": federation_serializers.APIActorSerializer(actor).data,
+        "cover": common_serializers.AttachmentSerializer(track.attachment_cover).data,
     }
     serializer = serializers.TrackSerializer(track)
     assert serializer.data == expected
diff --git a/api/tests/music/test_tasks.py b/api/tests/music/test_tasks.py
index 3f09c537838667c12fa382351bcb6b762300ebcc..053f62ede5ba4230e450dd2873312ae7d6564f1c 100644
--- a/api/tests/music/test_tasks.py
+++ b/api/tests/music/test_tasks.py
@@ -1,5 +1,4 @@
 import datetime
-import io
 import os
 import pytest
 import uuid
@@ -270,7 +269,7 @@ def test_can_create_track_from_file_metadata_distinct_position(factories):
     assert new_track != track
 
 
-def test_can_create_track_from_file_metadata_federation(factories, mocker, r_mock):
+def test_can_create_track_from_file_metadata_federation(factories, mocker):
     metadata = {
         "artists": [
             {"name": "Artist", "fid": "https://artist.fid", "fdate": timezone.now()}
@@ -279,6 +278,7 @@ def test_can_create_track_from_file_metadata_federation(factories, mocker, r_moc
             "title": "Album",
             "fid": "https://album.fid",
             "fdate": timezone.now(),
+            "cover_data": {"url": "https://cover/hello.png", "mimetype": "image/png"},
             "artists": [
                 {
                     "name": "Album artist",
@@ -291,9 +291,7 @@ def test_can_create_track_from_file_metadata_federation(factories, mocker, r_moc
         "position": 4,
         "fid": "https://hello",
         "fdate": timezone.now(),
-        "cover_data": {"url": "https://cover/hello.png", "mimetype": "image/png"},
     }
-    r_mock.get(metadata["cover_data"]["url"], body=io.BytesIO(b"coucou"))
 
     track = tasks.get_track_from_import_metadata(metadata, update_cover=True)
 
@@ -301,10 +299,11 @@ def test_can_create_track_from_file_metadata_federation(factories, mocker, r_moc
     assert track.fid == metadata["fid"]
     assert track.creation_date == metadata["fdate"]
     assert track.position == 4
-    assert track.album.attachment_cover.file.read() == b"coucou"
-    assert track.album.attachment_cover.file.path.endswith(".png")
-    assert track.album.attachment_cover.url == metadata["cover_data"]["url"]
-    assert track.album.attachment_cover.mimetype == metadata["cover_data"]["mimetype"]
+    assert track.album.attachment_cover.url == metadata["album"]["cover_data"]["url"]
+    assert (
+        track.album.attachment_cover.mimetype
+        == metadata["album"]["cover_data"]["mimetype"]
+    )
 
     assert track.album.fid == metadata["album"]["fid"]
     assert track.album.title == metadata["album"]["title"]
@@ -328,7 +327,9 @@ def test_sort_candidates(factories):
 
 def test_upload_import(now, factories, temp_signal, mocker):
     outbox = mocker.patch("funkwhale_api.federation.routes.outbox.dispatch")
-    update_album_cover = mocker.patch("funkwhale_api.music.tasks.update_album_cover")
+    populate_album_cover = mocker.patch(
+        "funkwhale_api.music.tasks.populate_album_cover"
+    )
     get_picture = mocker.patch("funkwhale_api.music.metadata.Metadata.get_picture")
     get_track_from_import_metadata = mocker.spy(tasks, "get_track_from_import_metadata")
     track = factories["music.Track"](album__attachment_cover=None)
@@ -348,8 +349,8 @@ def test_upload_import(now, factories, temp_signal, mocker):
     assert upload.import_status == "finished"
     assert upload.import_date == now
     get_picture.assert_called_once_with("cover_front", "other")
-    update_album_cover.assert_called_once_with(
-        upload.track.album, cover_data=get_picture.return_value, source=upload.source
+    populate_album_cover.assert_called_once_with(
+        upload.track.album, source=upload.source
     )
     assert (
         get_track_from_import_metadata.call_args[-1]["attributed_to"]
@@ -557,46 +558,33 @@ def test_upload_import_error_metadata(factories, now, temp_signal, mocker):
 
 
 def test_upload_import_updates_cover_if_no_cover(factories, mocker, now):
-    mocked_update = mocker.patch("funkwhale_api.music.tasks.update_album_cover")
+    populate_album_cover = mocker.patch(
+        "funkwhale_api.music.tasks.populate_album_cover"
+    )
     album = factories["music.Album"](attachment_cover=None)
     track = factories["music.Track"](album=album)
     upload = factories["music.Upload"](
         track=None, import_metadata={"funkwhale": {"track": {"uuid": track.uuid}}}
     )
     tasks.process_upload(upload_id=upload.pk)
-    mocked_update.assert_called_once_with(album, source=None, cover_data=None)
-
-
-def test_update_album_cover_mbid(factories, mocker):
-    album = factories["music.Album"](attachment_cover=None)
-
-    mocked_get = mocker.patch("funkwhale_api.music.models.Album.get_image")
-    tasks.update_album_cover(album=album)
-
-    mocked_get.assert_called_once_with()
-
-
-def test_update_album_cover_file_data(factories, mocker):
-    album = factories["music.Album"](attachment_cover=None, mbid=None)
-
-    mocked_get = mocker.patch("funkwhale_api.music.models.Album.get_image")
-    tasks.update_album_cover(album=album, cover_data={"hello": "world"})
-    mocked_get.assert_called_once_with(data={"hello": "world"})
+    populate_album_cover.assert_called_once_with(album, source=None)
 
 
 @pytest.mark.parametrize("ext,mimetype", [("jpg", "image/jpeg"), ("png", "image/png")])
-def test_update_album_cover_file_cover_separate_file(ext, mimetype, factories, mocker):
+def test_populate_album_cover_file_cover_separate_file(
+    ext, mimetype, factories, mocker
+):
     mocker.patch("funkwhale_api.music.tasks.IMAGE_TYPES", [(ext, mimetype)])
     image_path = os.path.join(DATA_DIR, "cover.{}".format(ext))
     with open(image_path, "rb") as f:
         image_content = f.read()
     album = factories["music.Album"](attachment_cover=None, mbid=None)
 
-    mocked_get = mocker.patch("funkwhale_api.music.models.Album.get_image")
+    attach_file = mocker.patch("funkwhale_api.common.utils.attach_file")
     mocker.patch("funkwhale_api.music.metadata.Metadata.get_picture", return_value=None)
-    tasks.update_album_cover(album=album, source="file://" + image_path)
-    mocked_get.assert_called_once_with(
-        data={"mimetype": mimetype, "content": image_content}
+    tasks.populate_album_cover(album=album, source="file://" + image_path)
+    attach_file.assert_called_once_with(
+        album, "attachment_cover", {"mimetype": mimetype, "content": image_content}
     )
 
 
@@ -623,6 +611,11 @@ def test_federation_audio_track_to_metadata(now, mocker):
         "attributedTo": "http://track.attributed",
         "tag": [{"type": "Hashtag", "name": "TrackTag"}],
         "content": "hello there",
+        "image": {
+            "type": "Link",
+            "href": "http://cover.test/track",
+            "mediaType": "image/png",
+        },
         "album": {
             "published": published.isoformat(),
             "type": "Album",
@@ -645,6 +638,11 @@ def test_federation_audio_track_to_metadata(now, mocker):
                     "musicbrainzId": str(uuid.uuid4()),
                     "attributedTo": "http://album-artist.attributed",
                     "tag": [{"type": "Hashtag", "name": "AlbumArtistTag"}],
+                    "image": {
+                        "type": "Link",
+                        "href": "http://cover.test/album-artist",
+                        "mediaType": "image/png",
+                    },
                 }
             ],
             "cover": {
@@ -664,6 +662,11 @@ def test_federation_audio_track_to_metadata(now, mocker):
                 "musicbrainzId": str(uuid.uuid4()),
                 "attributedTo": "http://artist.attributed",
                 "tag": [{"type": "Hashtag", "name": "ArtistTag"}],
+                "image": {
+                    "type": "Link",
+                    "href": "http://cover.test/artist",
+                    "mediaType": "image/png",
+                },
             }
         ],
     }
@@ -681,6 +684,10 @@ def test_federation_audio_track_to_metadata(now, mocker):
         "attributed_to": references["http://track.attributed"],
         "tags": ["TrackTag"],
         "description": {"content_type": "text/html", "text": "hello there"},
+        "cover_data": {
+            "mimetype": serializer.validated_data["image"]["mediaType"],
+            "url": serializer.validated_data["image"]["href"],
+        },
         "album": {
             "title": payload["album"]["name"],
             "attributed_to": references["http://album.attributed"],
@@ -690,6 +697,10 @@ def test_federation_audio_track_to_metadata(now, mocker):
             "fdate": serializer.validated_data["album"]["published"],
             "tags": ["AlbumTag"],
             "description": {"content_type": "text/plain", "text": "album desc"},
+            "cover_data": {
+                "mimetype": serializer.validated_data["album"]["cover"]["mediaType"],
+                "url": serializer.validated_data["album"]["cover"]["href"],
+            },
             "artists": [
                 {
                     "name": a["name"],
@@ -704,6 +715,14 @@ def test_federation_audio_track_to_metadata(now, mocker):
                         "text": "album artist desc",
                     },
                     "tags": ["AlbumArtistTag"],
+                    "cover_data": {
+                        "mimetype": serializer.validated_data["album"]["artists"][i][
+                            "image"
+                        ]["mediaType"],
+                        "url": serializer.validated_data["album"]["artists"][i][
+                            "image"
+                        ]["href"],
+                    },
                 }
                 for i, a in enumerate(payload["album"]["artists"])
             ],
@@ -719,13 +738,15 @@ def test_federation_audio_track_to_metadata(now, mocker):
                 "attributed_to": references["http://artist.attributed"],
                 "tags": ["ArtistTag"],
                 "description": {"content_type": "text/html", "text": "artist desc"},
+                "cover_data": {
+                    "mimetype": serializer.validated_data["artists"][i]["image"][
+                        "mediaType"
+                    ],
+                    "url": serializer.validated_data["artists"][i]["image"]["href"],
+                },
             }
             for i, a in enumerate(payload["artists"])
         ],
-        "cover_data": {
-            "mimetype": serializer.validated_data["album"]["cover"]["mediaType"],
-            "url": serializer.validated_data["album"]["cover"]["href"],
-        },
     }
 
     result = tasks.federation_audio_track_to_metadata(
@@ -764,7 +785,7 @@ def test_scan_page_fetches_page_and_creates_tracks(now, mocker, factories, r_moc
     scan_page = mocker.patch("funkwhale_api.music.tasks.scan_library_page.delay")
     scan = factories["music.LibraryScan"](status="scanning", total_files=5)
     uploads = [
-        factories["music.Upload"].build(
+        factories["music.Upload"](
             fid="https://track.test/{}".format(i),
             size=42,
             bitrate=66,
@@ -780,7 +801,9 @@ def test_scan_page_fetches_page_and_creates_tracks(now, mocker, factories, r_moc
         "page": Paginator(uploads, 3).page(1),
         "item_serializer": federation_serializers.UploadSerializer,
     }
+    uploads[0].__class__.objects.filter(pk__in=[u.pk for u in uploads]).delete()
     page = federation_serializers.CollectionPageSerializer(page_conf)
+
     r_mock.get(page.data["id"], json=page.data)
 
     tasks.scan_library_page(library_scan_id=scan.pk, page_url=page.data["id"])
@@ -1129,3 +1152,15 @@ def test_tag_artists_from_tracks(queryset_equal_queries, factories, mocker):
     add_tags_batch.assert_called_once_with(
         get_tags_from_foreign_key.return_value, model=models.Artist,
     )
+
+
+def test_can_download_image_file_for_album_mbid(binary_cover, mocker, factories):
+    mocker.patch(
+        "funkwhale_api.musicbrainz.api.images.get_front", return_value=binary_cover
+    )
+    # client._api.get_image_front('55ea4f82-b42b-423e-a0e5-290ccdf443ed')
+    album = factories["music.Album"](mbid="55ea4f82-b42b-423e-a0e5-290ccdf443ed")
+    tasks.populate_album_cover(album, replace=True)
+
+    assert album.attachment_cover.file.read() == binary_cover
+    assert album.attachment_cover.mimetype == "image/jpeg"
diff --git a/front/src/components/audio/artist/Card.vue b/front/src/components/audio/artist/Card.vue
index 71192914e1ba8c430f5ef0de3da4ca4398eaa14e..6c9d54eccd8fc85cc3e07e41a118aa171105864c 100644
--- a/front/src/components/audio/artist/Card.vue
+++ b/front/src/components/audio/artist/Card.vue
@@ -51,6 +51,9 @@ export default {
       return url
     },
     cover () {
+      if (this.artist.cover) {
+        return this.artist.cover
+      }
       return this.artist.albums.map((a) => {
         return a.cover
       }).filter((c) => {
diff --git a/front/src/components/library/ArtistBase.vue b/front/src/components/library/ArtistBase.vue
index 2c5d5284aac8b1c39016f854830795a9de785e9b..1c9054efdf597fc094d1c667d22c54113512bbb6 100644
--- a/front/src/components/library/ArtistBase.vue
+++ b/front/src/components/library/ArtistBase.vue
@@ -230,6 +230,9 @@ export default {
       )
     },
     cover() {
+      if (this.object.cover) {
+        return this.object.cover
+      }
       return this.object.albums
         .filter(album => {
           return album.cover
diff --git a/front/src/edits.js b/front/src/edits.js
index 0757ed0d4a169873984e0ebc192beac14ef74a41..baa9bbb328ca5d990c748725bc3178a601c74507 100644
--- a/front/src/edits.js
+++ b/front/src/edits.js
@@ -19,6 +19,19 @@ export default {
       getValue: (obj) => { return obj.description || {text: null, content_type: 'text/markdown'}},
       getValueRepr: getContentValueRepr
     }
+    const cover = {
+      id: 'cover',
+      type: 'attachment',
+      required: false,
+      label: this.$pgettext('Content/*/*/Noun', 'Cover'),
+      getValue: (obj) => {
+        if (obj.cover) {
+          return obj.cover.uuid
+        } else {
+          return null
+        }
+      }
+    }
     return {
       artist: {
         fields: [
@@ -30,6 +43,7 @@ export default {
             getValue: (obj) => { return obj.name }
           },
           description,
+          cover,
           {
             id: 'tags',
             type: 'tags',
@@ -57,19 +71,7 @@ export default {
             label: this.$pgettext('Content/*/*/Noun', 'Release date'),
             getValue: (obj) => { return obj.release_date }
           },
-          {
-            id: 'cover',
-            type: 'attachment',
-            required: false,
-            label: this.$pgettext('Content/*/*/Noun', 'Cover'),
-            getValue: (obj) => {
-              if (obj.cover) {
-                return obj.cover.uuid
-              } else {
-                return null
-              }
-            }
-          },
+          cover,
           {
             id: 'tags',
             type: 'tags',
@@ -90,6 +92,7 @@ export default {
             getValue: (obj) => { return obj.title }
           },
           description,
+          cover,
           {
             id: 'position',
             type: 'text',
diff --git a/front/src/views/admin/library/ArtistDetail.vue b/front/src/views/admin/library/ArtistDetail.vue
index 72e1bcb0848b0e4acbcb6964d1cb789bd59bdf16..0104431eaf2ee9ea16c330b89f07f7fc84946752 100644
--- a/front/src/views/admin/library/ArtistDetail.vue
+++ b/front/src/views/admin/library/ArtistDetail.vue
@@ -9,7 +9,8 @@
           <div class="ui column">
             <div class="segment-content">
               <h2 class="ui header">
-                <i class="circular inverted user icon"></i>
+                <img v-if="object.cover" v-lazy="$store.getters['instance/absoluteUrl'](object.cover.square_crop)">
+                <img v-else src="../../../assets/audio/default-cover.png">
                 <div class="content">
                   {{ object.name | truncate(100) }}
                   <div class="sub header">
diff --git a/front/src/views/admin/library/TrackDetail.vue b/front/src/views/admin/library/TrackDetail.vue
index d7f836d3a97d7c29a7ced121584e92cf69e9c427..e85acef02c0919fded156a0f756dadc159c647c1 100644
--- a/front/src/views/admin/library/TrackDetail.vue
+++ b/front/src/views/admin/library/TrackDetail.vue
@@ -9,7 +9,8 @@
           <div class="ui column">
             <div class="segment-content">
               <h2 class="ui header">
-                <i class="circular inverted user icon"></i>
+                <img v-if="object.cover" v-lazy="$store.getters['instance/absoluteUrl'](object.cover.square_crop)">
+                <img v-else src="../../../assets/audio/default-cover.png">
                 <div class="content">
                   {{ object.title | truncate(100) }}
                   <div class="sub header">