diff --git a/api/funkwhale_api/common/models.py b/api/funkwhale_api/common/models.py
index 1b70304eddad9b0ddd013a3cb9d3aecd9c5db0e6..c10d8dd067d59d5a2e60f2bb598a200f62ff6f41 100644
--- a/api/funkwhale_api/common/models.py
+++ b/api/funkwhale_api/common/models.py
@@ -180,6 +180,7 @@ class AttachmentQuerySet(models.QuerySet):
             "mutation_attachment",
             "covered_track",
             "covered_artist",
+            "iconed_actor",
         ]
         query = None
         for field in related_fields:
diff --git a/api/funkwhale_api/common/scripts/create_image_variations.py b/api/funkwhale_api/common/scripts/create_image_variations.py
index 31bf0269c3602a917014d819b7dacf387f965052..10bef7a35179f7e27b744f16fd68f9ef684d6054 100644
--- a/api/funkwhale_api/common/scripts/create_image_variations.py
+++ b/api/funkwhale_api/common/scripts/create_image_variations.py
@@ -5,13 +5,9 @@ Compute different sizes of image used for Album covers and User avatars
 from versatileimagefield.image_warmer import VersatileImageFieldWarmer
 
 from funkwhale_api.common.models import Attachment
-from funkwhale_api.music.models import Album
-from funkwhale_api.users.models import User
 
 
 MODELS = [
-    (Album, "cover", "square"),
-    (User, "avatar", "square"),
     (Attachment, "file", "attachment_square"),
 ]
 
diff --git a/api/funkwhale_api/common/serializers.py b/api/funkwhale_api/common/serializers.py
index 38be2b5bc14551d022a10469399df647b4cc92bd..c754540c924a276f61f7ab9aad5fecb237a5418e 100644
--- a/api/funkwhale_api/common/serializers.py
+++ b/api/funkwhale_api/common/serializers.py
@@ -24,6 +24,7 @@ class RelatedField(serializers.RelatedField):
         self.related_field_name = related_field_name
         self.serializer = serializer
         self.filters = kwargs.pop("filters", None)
+        self.queryset_filter = kwargs.pop("queryset_filter", None)
         try:
             kwargs["queryset"] = kwargs.pop("queryset")
         except KeyError:
@@ -36,10 +37,16 @@ class RelatedField(serializers.RelatedField):
             filters.update(self.filters(self.context))
         return filters
 
+    def filter_queryset(self, queryset):
+        if self.queryset_filter:
+            queryset = self.queryset_filter(queryset, self.context)
+        return queryset
+
     def to_internal_value(self, data):
         try:
             queryset = self.get_queryset()
             filters = self.get_filters(data)
+            queryset = self.filter_queryset(queryset)
             return queryset.get(**filters)
         except ObjectDoesNotExist:
             self.fail(
@@ -318,3 +325,16 @@ class ContentSerializer(serializers.Serializer):
 
     def get_html(self, o):
         return utils.render_html(o.text, o.content_type)
+
+
+class NullToEmptDict(object):
+    def get_attribute(self, o):
+        attr = super().get_attribute(o)
+        if attr is None:
+            return {}
+        return attr
+
+    def to_representation(self, v):
+        if not v:
+            return v
+        return super().to_representation(v)
diff --git a/api/funkwhale_api/common/utils.py b/api/funkwhale_api/common/utils.py
index 9fb9f64e2928eb693646d57736824c02d22bf947..d7135c4a0303c621b9bf34fb78268a0a5336f23f 100644
--- a/api/funkwhale_api/common/utils.py
+++ b/api/funkwhale_api/common/utils.py
@@ -327,8 +327,11 @@ def attach_file(obj, field, file_data, fetch=False):
     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)
+    name_fields = ["uuid", "full_username", "pk"]
+    name = [getattr(obj, field) for field in name_fields if getattr(obj, field, None)][
+        0
+    ]
+    filename = "{}-{}.{}".format(field, name, extension)
     if "url" in file_data:
         attachment.url = file_data["url"]
     else:
diff --git a/api/funkwhale_api/favorites/views.py b/api/funkwhale_api/favorites/views.py
index 3e1f337593624bbc0b338512a66b04754d8f1e74..db0c909001edef6f854216a9a22895a2a42c0fb0 100644
--- a/api/funkwhale_api/favorites/views.py
+++ b/api/funkwhale_api/favorites/views.py
@@ -22,7 +22,9 @@ class TrackFavoriteViewSet(
 
     filterset_class = filters.TrackFavoriteFilter
     serializer_class = serializers.UserTrackFavoriteSerializer
-    queryset = models.TrackFavorite.objects.all().select_related("user__actor")
+    queryset = models.TrackFavorite.objects.all().select_related(
+        "user__actor__attachment_icon"
+    )
     permission_classes = [
         oauth_permissions.ScopePermission,
         permissions.OwnerPermission,
diff --git a/api/funkwhale_api/federation/migrations/0024_actor_attachment_icon.py b/api/funkwhale_api/federation/migrations/0024_actor_attachment_icon.py
new file mode 100644
index 0000000000000000000000000000000000000000..66a888e0f35c4a641cc27b2c7f010b5ee0ad9d87
--- /dev/null
+++ b/api/funkwhale_api/federation/migrations/0024_actor_attachment_icon.py
@@ -0,0 +1,20 @@
+# Generated by Django 2.2.9 on 2020-01-23 13:59
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('common', '0007_auto_20200116_1610'),
+        ('federation', '0023_actor_summary_obj'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='actor',
+            name='attachment_icon',
+            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='iconed_actor', to='common.Attachment'),
+        ),
+    ]
diff --git a/api/funkwhale_api/federation/models.py b/api/funkwhale_api/federation/models.py
index 211ac944717b396cfcfd53d86542ddaa4da70352..2592afb16cde62f394aac84f6adcfee651a6cdaa 100644
--- a/api/funkwhale_api/federation/models.py
+++ b/api/funkwhale_api/federation/models.py
@@ -205,6 +205,13 @@ class Actor(models.Model):
         through_fields=("target", "actor"),
         related_name="following",
     )
+    attachment_icon = models.ForeignKey(
+        "common.Attachment",
+        null=True,
+        blank=True,
+        on_delete=models.SET_NULL,
+        related_name="iconed_actor",
+    )
 
     objects = ActorQuerySet.as_manager()
 
diff --git a/api/funkwhale_api/federation/serializers.py b/api/funkwhale_api/federation/serializers.py
index 4883062b313b8e2d63026351b01eb0e95bdf22db..96ff2fcd37d71c4db5220ebe4ed19dec551bad90 100644
--- a/api/funkwhale_api/federation/serializers.py
+++ b/api/funkwhale_api/federation/serializers.py
@@ -1,9 +1,7 @@
 import logging
-import mimetypes
 import urllib.parse
 import uuid
 
-from django.core.exceptions import ObjectDoesNotExist
 from django.core.paginator import Paginator
 from django.db import transaction
 
@@ -97,6 +95,9 @@ class ActorSerializer(jsonld.JsonLdSerializer):
     following = serializers.URLField(max_length=500, required=False, allow_null=True)
     publicKey = PublicKeySerializer(required=False)
     endpoints = EndpointsSerializer(required=False)
+    icon = LinkSerializer(
+        allowed_mimetypes=["image/*"], allow_null=True, required=False
+    )
 
     class Meta:
         jsonld_mapping = {
@@ -113,6 +114,7 @@ class ActorSerializer(jsonld.JsonLdSerializer):
             ),
             "mediaType": jsonld.first_val(contexts.AS.mediaType),
             "endpoints": jsonld.first_obj(contexts.AS.endpoints),
+            "icon": jsonld.first_obj(contexts.AS.icon),
         }
 
     def to_representation(self, instance):
@@ -143,17 +145,11 @@ class ActorSerializer(jsonld.JsonLdSerializer):
                 "id": "{}#main-key".format(instance.fid),
             }
         ret["endpoints"] = {}
+
+        include_image(ret, instance.attachment_icon, "icon")
+
         if instance.shared_inbox_url:
             ret["endpoints"]["sharedInbox"] = instance.shared_inbox_url
-        try:
-            if instance.user.avatar:
-                ret["icon"] = {
-                    "type": "Image",
-                    "mediaType": mimetypes.guess_type(instance.user.avatar_path)[0],
-                    "url": utils.full_url(instance.user.avatar.crop["400x400"].url),
-                }
-        except ObjectDoesNotExist:
-            pass
         return ret
 
     def prepare_missing_fields(self):
@@ -201,6 +197,15 @@ class ActorSerializer(jsonld.JsonLdSerializer):
         common_utils.attach_content(
             actor, "summary_obj", self.validated_data["summary"]
         )
+        if "icon" in self.validated_data:
+            new_value = self.validated_data["icon"]
+            common_utils.attach_file(
+                actor,
+                "attachment_icon",
+                {"url": new_value["href"], "mimetype": new_value["mediaType"]}
+                if new_value
+                else None,
+            )
         return actor
 
     def validate(self, data):
@@ -844,15 +849,15 @@ def include_content(repr, content_obj):
     repr["mediaType"] = "text/html"
 
 
-def include_image(repr, attachment):
+def include_image(repr, attachment, field="image"):
     if attachment:
-        repr["image"] = {
+        repr[field] = {
             "type": "Image",
             "href": attachment.download_url_original,
             "mediaType": attachment.mimetype or "image/jpeg",
         }
     else:
-        repr["image"] = None
+        repr[field] = None
 
 
 class MusicEntitySerializer(jsonld.JsonLdSerializer):
diff --git a/api/funkwhale_api/history/views.py b/api/funkwhale_api/history/views.py
index 6cdbc8a80f848c074adde2dee2f04f4ff5eefec1..56afadf4046c2ca984605b1986ea6920d400d1de 100644
--- a/api/funkwhale_api/history/views.py
+++ b/api/funkwhale_api/history/views.py
@@ -19,7 +19,9 @@ class ListeningViewSet(
 ):
 
     serializer_class = serializers.ListeningSerializer
-    queryset = models.Listening.objects.all().select_related("user__actor")
+    queryset = models.Listening.objects.all().select_related(
+        "user__actor__attachment_icon"
+    )
 
     permission_classes = [
         oauth_permissions.ScopePermission,
diff --git a/api/funkwhale_api/music/mutations.py b/api/funkwhale_api/music/mutations.py
index c208d93a1fdd4be3b4b4d72ce144a030b5ddcb91..d95d3570250f82a1468542dc2d819c97042eb50f 100644
--- a/api/funkwhale_api/music/mutations.py
+++ b/api/funkwhale_api/music/mutations.py
@@ -61,7 +61,9 @@ class DescriptionMutation(mutations.UpdateMutationSerializer):
 
 class CoverMutation(mutations.UpdateMutationSerializer):
     cover = common_serializers.RelatedField(
-        "uuid", queryset=common_models.Attachment.objects.all().local(), serializer=None
+        "uuid",
+        queryset=common_models.Attachment.objects.all().local(),
+        serializer=None,
     )
 
     def get_serialized_relations(self):
diff --git a/api/funkwhale_api/music/serializers.py b/api/funkwhale_api/music/serializers.py
index 27cca51da17fbb74cbd0c21c575f8b8fb2679926..a6ca51f0aacc31b58e764572f810d66ee4af3de5 100644
--- a/api/funkwhale_api/music/serializers.py
+++ b/api/funkwhale_api/music/serializers.py
@@ -18,20 +18,9 @@ from funkwhale_api.tags import serializers as tags_serializers
 from . import filters, models, tasks
 
 
-class NullToEmptDict(object):
-    def get_attribute(self, o):
-        attr = super().get_attribute(o)
-        if attr is None:
-            return {}
-        return attr
-
-    def to_representation(self, v):
-        if not v:
-            return v
-        return super().to_representation(v)
-
-
-class CoverField(NullToEmptDict, common_serializers.AttachmentSerializer):
+class CoverField(
+    common_serializers.NullToEmptDict, common_serializers.AttachmentSerializer
+):
     # XXX: BACKWARD COMPATIBILITY
     pass
 
diff --git a/api/funkwhale_api/playlists/views.py b/api/funkwhale_api/playlists/views.py
index f2ade81814411399d51d0674031f497af39f0490..2cc348f460b3180d7df8c4ca4e88e2eab19c92cb 100644
--- a/api/funkwhale_api/playlists/views.py
+++ b/api/funkwhale_api/playlists/views.py
@@ -23,7 +23,7 @@ class PlaylistViewSet(
     serializer_class = serializers.PlaylistSerializer
     queryset = (
         models.Playlist.objects.all()
-        .select_related("user__actor")
+        .select_related("user__actor__attachment_icon")
         .annotate(tracks_count=Count("playlist_tracks", distinct=True))
         .with_covers()
         .with_duration()
diff --git a/api/funkwhale_api/users/migrations/0017_actor_avatar.py b/api/funkwhale_api/users/migrations/0017_actor_avatar.py
new file mode 100644
index 0000000000000000000000000000000000000000..c97a5fe13fe3375530473e0f2ffe3fc61d71797f
--- /dev/null
+++ b/api/funkwhale_api/users/migrations/0017_actor_avatar.py
@@ -0,0 +1,52 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import migrations
+
+
+def create_attachments(apps, schema_editor):
+    Actor = apps.get_model("federation", "Actor")
+    User = apps.get_model("users", "User")
+    Attachment = apps.get_model("common", "Attachment")
+
+    obj_attachment_mapping = {}
+    def get_mimetype(path):
+        if path.lower().endswith('.png'):
+            return "image/png"
+        return "image/jpeg"
+    qs = User.objects.filter(actor__attachment_icon=None).exclude(avatar="").exclude(avatar=None).exclude(actor=None).select_related('actor')
+    total = qs.count()
+    print('Creating attachments for {} user avatars, this may take a while…'.format(total))
+    from django.core.files.storage import FileSystemStorage
+    for i, user in enumerate(qs):
+        if isinstance(user.avatar.storage._wrapped, FileSystemStorage):
+            try:
+                size = user.avatar.size
+            except FileNotFoundError:
+                # can occur when file isn't found on disk or S3
+                print("  Warning: avatar file wasn't found in storage: {}".format(e.__class__))
+                size = None
+        obj_attachment_mapping[user.actor] = Attachment(
+            file=user.avatar,
+            size=size,
+            mimetype=get_mimetype(user.avatar.name),
+        )
+    print('Commiting changes…')
+    Attachment.objects.bulk_create(obj_attachment_mapping.values(), batch_size=2000)
+    # map each attachment to the corresponding obj
+    # and bulk save
+    for obj, attachment in obj_attachment_mapping.items():
+        obj.attachment_icon = attachment
+
+    Actor.objects.bulk_update(obj_attachment_mapping.keys(), fields=['attachment_icon'], batch_size=2000)
+
+
+def rewind(apps, schema_editor):
+    pass
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [("users", "0016_auto_20190920_0857"), ("federation", "0024_actor_attachment_icon")]
+
+    operations = [migrations.RunPython(create_attachments, rewind)]
diff --git a/api/funkwhale_api/users/models.py b/api/funkwhale_api/users/models.py
index 25eb926c7e37b8810e0ddc384630697575cfa859..72ab2afd669ccf8d1ec18e6428f9abb99d40a087 100644
--- a/api/funkwhale_api/users/models.py
+++ b/api/funkwhale_api/users/models.py
@@ -21,7 +21,6 @@ from django_auth_ldap.backend import populate_user as ldap_populate_user
 from oauth2_provider import models as oauth2_models
 from oauth2_provider import validators as oauth2_validators
 from versatileimagefield.fields import VersatileImageField
-from versatileimagefield.image_warmer import VersatileImageFieldWarmer
 
 from funkwhale_api.common import fields, preferences
 from funkwhale_api.common import utils as common_utils
@@ -413,13 +412,3 @@ def create_actor(user):
 def init_ldap_user(sender, user, ldap_user, **kwargs):
     if not user.actor:
         user.actor = create_actor(user)
-
-
-@receiver(models.signals.post_save, sender=User)
-def warm_user_avatar(sender, instance, **kwargs):
-    if not instance.avatar or not settings.CREATE_IMAGE_THUMBNAILS:
-        return
-    user_avatar_warmer = VersatileImageFieldWarmer(
-        instance_or_queryset=instance, rendition_key_set="square", image_attr="avatar"
-    )
-    num_created, failed_to_create = user_avatar_warmer.warm()
diff --git a/api/funkwhale_api/users/serializers.py b/api/funkwhale_api/users/serializers.py
index 0d167b1a1b7fd8c181f1c3abd587973bb89af62c..59986fdaa84ac107ce7102a39f1de9c4217fe64a 100644
--- a/api/funkwhale_api/users/serializers.py
+++ b/api/funkwhale_api/users/serializers.py
@@ -7,9 +7,9 @@ from django.utils.translation import gettext_lazy as _
 from rest_auth.serializers import PasswordResetSerializer as PRS
 from rest_auth.registration.serializers import RegisterSerializer as RS, get_adapter
 from rest_framework import serializers
-from versatileimagefield.serializers import VersatileImageFieldSerializer
 
 from funkwhale_api.activity import serializers as activity_serializers
+from funkwhale_api.common import models as common_models
 from funkwhale_api.common import serializers as common_serializers
 from funkwhale_api.common import utils as common_utils
 from funkwhale_api.federation import models as federation_models
@@ -89,26 +89,30 @@ class UserActivitySerializer(activity_serializers.ModelSerializer):
         return "Person"
 
 
-class AvatarField(
-    common_serializers.StripExifImageField, VersatileImageFieldSerializer
-):
-    pass
-
-
-avatar_field = AvatarField(allow_null=True, sizes="square")
-
-
 class UserBasicSerializer(serializers.ModelSerializer):
-    avatar = avatar_field
+    avatar = serializers.SerializerMethodField()
 
     class Meta:
         model = models.User
         fields = ["id", "username", "name", "date_joined", "avatar"]
 
+    def get_avatar(self, o):
+        return common_serializers.AttachmentSerializer(
+            o.actor.attachment_icon if o.actor else None
+        ).data
+
 
 class UserWriteSerializer(serializers.ModelSerializer):
-    avatar = avatar_field
     summary = common_serializers.ContentSerializer(required=False, allow_null=True)
+    avatar = common_serializers.RelatedField(
+        "uuid",
+        queryset=common_models.Attachment.objects.all().local().attached(False),
+        serializer=None,
+        queryset_filter=lambda qs, context: qs.filter(
+            actor=context["request"].user.actor
+        ),
+        write_only=True,
+    )
 
     class Meta:
         model = models.User
@@ -125,19 +129,30 @@ class UserWriteSerializer(serializers.ModelSerializer):
         if not obj.actor:
             obj.create_actor()
         summary = validated_data.pop("summary", NOOP)
+        avatar = validated_data.pop("avatar", NOOP)
+
         obj = super().update(obj, validated_data)
 
         if summary != NOOP:
             common_utils.attach_content(obj.actor, "summary_obj", summary)
-
+        if avatar != NOOP:
+            obj.actor.attachment_icon = avatar
+            obj.actor.save(update_fields=["attachment_icon"])
         return obj
 
+    def to_representation(self, obj):
+        repr = super().to_representation(obj)
+        repr["avatar"] = common_serializers.AttachmentSerializer(
+            obj.actor.attachment_icon
+        ).data
+        return repr
+
 
 class UserReadSerializer(serializers.ModelSerializer):
 
     permissions = serializers.SerializerMethodField()
     full_username = serializers.SerializerMethodField()
-    avatar = avatar_field
+    avatar = serializers.SerializerMethodField()
 
     class Meta:
         model = models.User
@@ -155,6 +170,9 @@ class UserReadSerializer(serializers.ModelSerializer):
             "avatar",
         ]
 
+    def get_avatar(self, o):
+        return common_serializers.AttachmentSerializer(o.actor.attachment_icon).data
+
     def get_permissions(self, o):
         return o.get_permissions()
 
diff --git a/api/funkwhale_api/users/views.py b/api/funkwhale_api/users/views.py
index 28189c4bc547e883ee4f5ee1227923fa7e07ca67..7e94f34a67807cdc4320d375edd642702810ba52 100644
--- a/api/funkwhale_api/users/views.py
+++ b/api/funkwhale_api/users/views.py
@@ -44,7 +44,7 @@ class PasswordResetConfirmView(rest_auth_views.PasswordResetConfirmView):
 
 
 class UserViewSet(mixins.UpdateModelMixin, viewsets.GenericViewSet):
-    queryset = models.User.objects.all()
+    queryset = models.User.objects.all().select_related("actor__attachment_icon")
     serializer_class = serializers.UserWriteSerializer
     lookup_field = "username"
     lookup_value_regex = r"[a-zA-Z0-9-_.]+"
diff --git a/api/tests/federation/test_serializers.py b/api/tests/federation/test_serializers.py
index 93fd14ba17a928a57378c3332733c58514313dfe..555684e0750f73ebef199e0b2a03e5c4f9304f02 100644
--- a/api/tests/federation/test_serializers.py
+++ b/api/tests/federation/test_serializers.py
@@ -36,6 +36,11 @@ def test_actor_serializer_from_ap(db):
             "id": actor_url + "#main-key",
         },
         "endpoints": {"sharedInbox": "https://noop.url/federation/shared/inbox"},
+        "icon": {
+            "type": "Image",
+            "mediaType": "image/jpeg",
+            "href": "https://image.example/image.png",
+        },
     }
 
     serializer = serializers.ActorSerializer(data=payload)
@@ -60,6 +65,8 @@ def test_actor_serializer_from_ap(db):
     assert actor.private_key is None
     assert actor.public_key == payload["publicKey"]["publicKeyPem"]
     assert actor.domain_id == "test.federation"
+    assert actor.attachment_icon.url == payload["icon"]["href"]
+    assert actor.attachment_icon.mimetype == payload["icon"]["mediaType"]
 
 
 def test_actor_serializer_only_mandatory_field_from_ap(db):
@@ -90,7 +97,7 @@ def test_actor_serializer_only_mandatory_field_from_ap(db):
     assert actor.manually_approves_followers is None
 
 
-def test_actor_serializer_to_ap(db):
+def test_actor_serializer_to_ap(factories):
     expected = {
         "@context": jsonld.get_default_context(),
         "id": "https://test.federation/user",
@@ -122,12 +129,18 @@ def test_actor_serializer_to_ap(db):
         domain=models.Domain.objects.create(pk="test.federation"),
         type="Person",
         manually_approves_followers=False,
+        attachment_icon=factories["common.Attachment"](),
     )
 
     content = common_utils.attach_content(
         ac, "summary_obj", {"text": "hello world", "content_type": "text/markdown"}
     )
     expected["summary"] = content.rendered
+    expected["icon"] = {
+        "type": "Image",
+        "mediaType": "image/jpeg",
+        "href": utils.full_url(ac.attachment_icon.file.url),
+    }
     serializer = serializers.ActorSerializer(ac)
 
     assert serializer.data == expected
@@ -1133,6 +1146,7 @@ def test_local_actor_serializer_to_ap(factories):
         domain=models.Domain.objects.create(pk="test.federation"),
         type="Person",
         manually_approves_followers=False,
+        attachment_icon=factories["common.Attachment"](),
     )
     content = common_utils.attach_content(
         ac, "summary_obj", {"text": "hello world", "content_type": "text/markdown"}
@@ -1145,7 +1159,7 @@ def test_local_actor_serializer_to_ap(factories):
     expected["icon"] = {
         "type": "Image",
         "mediaType": "image/jpeg",
-        "url": utils.full_url(user.avatar.crop["400x400"].url),
+        "href": utils.full_url(ac.attachment_icon.file.url),
     }
     serializer = serializers.ActorSerializer(ac)
 
diff --git a/api/tests/users/test_views.py b/api/tests/users/test_views.py
index d01252621570aff72de0510d2cce051fb5ea1fc5..3b5cf4cd369ee7edbb076c80d1c3480539c8ca85 100644
--- a/api/tests/users/test_views.py
+++ b/api/tests/users/test_views.py
@@ -110,7 +110,9 @@ def test_can_fetch_data_from_api(api_client, factories):
     user = factories["users.User"](permission_library=True, with_actor=True)
     summary = {"content_type": "text/plain", "text": "Hello"}
     summary_obj = common_utils.attach_content(user.actor, "summary_obj", summary)
-
+    avatar = factories["common.Attachment"]()
+    user.actor.attachment_icon = avatar
+    user.actor.save()
     api_client.login(username=user.username, password="test")
     response = api_client.get(url)
     assert response.status_code == 200
@@ -120,6 +122,9 @@ def test_can_fetch_data_from_api(api_client, factories):
     assert response.data["email"] == user.email
     assert response.data["name"] == user.name
     assert response.data["permissions"] == user.get_permissions()
+    assert (
+        response.data["avatar"] == common_serializers.AttachmentSerializer(avatar).data
+    )
     assert (
         response.data["summary"]
         == common_serializers.ContentSerializer(summary_obj).data
@@ -301,18 +306,18 @@ def test_user_cannot_patch_another_user(method, logged_in_api_client, factories)
     assert response.status_code == 403
 
 
-def test_user_can_patch_their_own_avatar(logged_in_api_client, avatar):
+def test_user_can_patch_their_own_avatar(logged_in_api_client, factories):
     user = logged_in_api_client.user
+    actor = user.create_actor()
+    attachment = factories["common.Attachment"](actor=actor)
     url = reverse("api:v1:users:users-detail", kwargs={"username": user.username})
-    content = avatar.read()
-    avatar.seek(0)
-    payload = {"avatar": avatar}
+    payload = {"avatar": attachment.uuid}
     response = logged_in_api_client.patch(url, payload)
 
     assert response.status_code == 200
     user.refresh_from_db()
 
-    assert user.avatar.read() == content
+    assert user.actor.attachment_icon == attachment
 
 
 def test_creating_user_creates_actor_as_well(
diff --git a/front/src/components/Sidebar.vue b/front/src/components/Sidebar.vue
index da693b257c7e7b543893304c30561cefbf98b579..fdf36f88a36f75c08654f3cc407dd76ca09ac795 100644
--- a/front/src/components/Sidebar.vue
+++ b/front/src/components/Sidebar.vue
@@ -74,7 +74,7 @@
         </router-link>
         <div class="item">
           <div class="ui user-dropdown dropdown" >
-            <img class="ui avatar image" v-if="$store.state.auth.profile.avatar.square_crop" v-lazy="$store.getters['instance/absoluteUrl']($store.state.auth.profile.avatar.square_crop)" />
+            <img class="ui avatar image" v-if="$store.state.auth.profile.avatar.square_crop" :src="$store.getters['instance/absoluteUrl']($store.state.auth.profile.avatar.square_crop)" />
             <actor-avatar v-else :actor="{preferred_username: $store.state.auth.username, full_username: $store.state.auth.username}" />
             <div class="menu">
               <router-link class="item" :to="{name: 'profile', params: {username: $store.state.auth.username}}"><translate translate-context="*/*/*/Noun">Profile</translate></router-link>
diff --git a/front/src/components/auth/Settings.vue b/front/src/components/auth/Settings.vue
index 795a5c45e92d599d8d83eccd1474dcd86046702b..269e7ac29d2d183b4aa8d5502c866ced9aa66e1b 100644
--- a/front/src/components/auth/Settings.vue
+++ b/front/src/components/auth/Settings.vue
@@ -41,25 +41,13 @@
               <li v-for="error in avatarErrors">{{ error }}</li>
             </ul>
           </div>
-          <div class="ui stackable grid">
-            <div class="ui ten wide column">
-              <h3 class="ui header"><translate translate-context="Content/Settings/Title/Verb">Upload a new avatar</translate></h3>
-              <p><translate translate-context="Content/Settings/Paragraph">PNG, GIF or JPG. At most 2MB. Will be downscaled to 400x400px.</translate></p>
-              <input class="ui input" ref="avatar" type="file" />
-              <div class="ui hidden divider"></div>
-              <button @click="submitAvatar" :class="['ui', {'loading': isLoadingAvatar}, 'button']">
-                <translate translate-context="Content/Settings/Button.Label/Verb">Update avatar</translate>
-              </button>
-            </div>
-            <div class="ui six wide column">
-              <h3 class="ui header"><translate translate-context="Content/Settings/Title/Noun">Current avatar</translate></h3>
-              <img class="ui circular image" v-if="currentAvatar && currentAvatar.square_crop" v-lazy="$store.getters['instance/absoluteUrl'](currentAvatar.medium_square_crop)" />
-              <div class="ui hidden divider"></div>
-              <button @click="removeAvatar" v-if="currentAvatar && currentAvatar.square_crop" :class="['ui', {'loading': isLoadingAvatar}, ,'yellow', 'button']">
-                <translate translate-context="Content/Settings/Button.Label/Verb">Remove avatar</translate>
-              </button>
-            </div>
-          </div>
+          {{ }}
+          <attachment-input
+            :value="avatar.uuid"
+            @input="submitAvatar($event)"
+            :initial-value="initialAvatar"
+            :required="false"
+            @delete="avatar = {uuid: null}"></attachment-input>
         </div>
       </section>
 
@@ -315,12 +303,14 @@ import logger from "@/logging"
 import PasswordInput from "@/components/forms/PasswordInput"
 import SubsonicTokenForm from "@/components/auth/SubsonicTokenForm"
 import TranslationsMixin from "@/components/mixins/Translations"
+import AttachmentInput from '@/components/common/AttachmentInput'
 
 export default {
   mixins: [TranslationsMixin],
   components: {
     PasswordInput,
-    SubsonicTokenForm
+    SubsonicTokenForm,
+    AttachmentInput
   },
   data() {
     let d = {
@@ -328,7 +318,7 @@ export default {
       // properties that will be used in it
       old_password: "",
       new_password: "",
-      currentAvatar: this.$store.state.auth.profile.avatar,
+      avatar: {...(this.$store.state.auth.profile.avatar || {uuid: null})},
       passwordError: "",
       password: "",
       isLoading: false,
@@ -336,7 +326,6 @@ export default {
       isDeletingAccount: false,
       accountDeleteErrors: [],
       avatarErrors: [],
-      avatar: null,
       apps: [],
       ownedApps: [],
       settings: {
@@ -352,6 +341,7 @@ export default {
         }
       }
     }
+    d.initialAvatar = d.avatar.uuid
     d.settings.order.forEach(id => {
       d.settings.fields[id].value = d.settings.fields[id].initial
       d.settings.fields[id].id = id
@@ -437,44 +427,17 @@ export default {
         }
       )
     },
-    submitAvatar() {
+    submitAvatar(uuid) {
       this.isLoadingAvatar = true
       this.avatarErrors = []
       let self = this
-      this.avatar = this.$refs.avatar.files[0]
-      let formData = new FormData()
-      formData.append("avatar", this.avatar)
-      axios
-        .patch(`users/users/${this.$store.state.auth.username}/`, formData, {
-          headers: {
-            "Content-Type": "multipart/form-data"
-          }
-        })
-        .then(
-          response => {
-            this.isLoadingAvatar = false
-            self.currentAvatar = response.data.avatar
-            self.$store.commit("auth/avatar", self.currentAvatar)
-          },
-          error => {
-            self.isLoadingAvatar = false
-            self.avatarErrors = error.backendErrors
-          }
-        )
-    },
-    removeAvatar() {
-      this.isLoadingAvatar = true
-      let self = this
-      this.avatar = null
       axios
-        .patch(`users/users/${this.$store.state.auth.username}/`, {
-          avatar: null
-        })
+        .patch(`users/users/${this.$store.state.auth.username}/`, {avatar: uuid})
         .then(
           response => {
             this.isLoadingAvatar = false
-            self.currentAvatar = {}
-            self.$store.commit("auth/avatar", self.currentAvatar)
+            self.avatar = response.data.avatar
+            self.$store.commit("auth/avatar", response.data.avatar)
           },
           error => {
             self.isLoadingAvatar = false