diff --git a/api/funkwhale_api/federation/migrations/0023_actor_summary_obj.py b/api/funkwhale_api/federation/migrations/0023_actor_summary_obj.py
new file mode 100644
index 0000000000000000000000000000000000000000..48b29edfb63d6ba07805fcd68c4390cc45d2a0ae
--- /dev/null
+++ b/api/funkwhale_api/federation/migrations/0023_actor_summary_obj.py
@@ -0,0 +1,20 @@
+# Generated by Django 2.2.9 on 2020-01-22 11:01
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('common', '0007_auto_20200116_1610'),
+        ('federation', '0022_auto_20191204_1539'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='actor',
+            name='summary_obj',
+            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='common.Content'),
+        ),
+    ]
diff --git a/api/funkwhale_api/federation/models.py b/api/funkwhale_api/federation/models.py
index 60cf26054ee5b3bb1273f6a2e1f3914f981a532e..211ac944717b396cfcfd53d86542ddaa4da70352 100644
--- a/api/funkwhale_api/federation/models.py
+++ b/api/funkwhale_api/federation/models.py
@@ -189,6 +189,9 @@ class Actor(models.Model):
     name = models.CharField(max_length=200, null=True, blank=True)
     domain = models.ForeignKey(Domain, on_delete=models.CASCADE, related_name="actors")
     summary = models.CharField(max_length=500, null=True, blank=True)
+    summary_obj = models.ForeignKey(
+        "common.Content", null=True, blank=True, on_delete=models.SET_NULL
+    )
     preferred_username = models.CharField(max_length=200, null=True, blank=True)
     public_key = models.TextField(max_length=5000, null=True, blank=True)
     private_key = models.TextField(max_length=5000, null=True, blank=True)
diff --git a/api/funkwhale_api/federation/serializers.py b/api/funkwhale_api/federation/serializers.py
index c55c99ed070e7917d57797ad5b7b5854b089f9cb..4883062b313b8e2d63026351b01eb0e95bdf22db 100644
--- a/api/funkwhale_api/federation/serializers.py
+++ b/api/funkwhale_api/federation/serializers.py
@@ -21,6 +21,18 @@ from . import activity, actors, contexts, jsonld, models, tasks, utils
 logger = logging.getLogger(__name__)
 
 
+class TruncatedCharField(serializers.CharField):
+    def __init__(self, *args, **kwargs):
+        self.truncate_length = kwargs.pop("truncate_length")
+        super().__init__(*args, **kwargs)
+
+    def to_internal_value(self, v):
+        v = super().to_internal_value(v)
+        if v:
+            v = v[: self.truncate_length]
+        return v
+
+
 class LinkSerializer(jsonld.JsonLdSerializer):
     type = serializers.ChoiceField(choices=[contexts.AS.Link, contexts.AS.Image])
     href = serializers.URLField(max_length=500)
@@ -76,7 +88,11 @@ class ActorSerializer(jsonld.JsonLdSerializer):
     preferredUsername = serializers.CharField()
     manuallyApprovesFollowers = serializers.NullBooleanField(required=False)
     name = serializers.CharField(required=False, max_length=200)
-    summary = serializers.CharField(max_length=None, required=False)
+    summary = TruncatedCharField(
+        truncate_length=common_models.CONTENT_TEXT_MAX_LENGTH,
+        required=False,
+        allow_null=True,
+    )
     followers = serializers.URLField(max_length=500, required=False)
     following = serializers.URLField(max_length=500, required=False, allow_null=True)
     publicKey = PublicKeySerializer(required=False)
@@ -113,11 +129,12 @@ class ActorSerializer(jsonld.JsonLdSerializer):
             ret["followers"] = instance.followers_url
         if instance.following_url:
             ret["following"] = instance.following_url
-        if instance.summary:
-            ret["summary"] = instance.summary
         if instance.manually_approves_followers is not None:
             ret["manuallyApprovesFollowers"] = instance.manually_approves_followers
 
+        if instance.summary_obj_id:
+            ret["summary"] = instance.summary_obj.rendered
+
         ret["@context"] = jsonld.get_default_context()
         if instance.public_key:
             ret["publicKey"] = {
@@ -146,7 +163,6 @@ class ActorSerializer(jsonld.JsonLdSerializer):
             "inbox_url": self.validated_data.get("inbox"),
             "following_url": self.validated_data.get("following"),
             "followers_url": self.validated_data.get("followers"),
-            "summary": self.validated_data.get("summary"),
             "type": self.validated_data["type"],
             "name": self.validated_data.get("name"),
             "preferred_username": self.validated_data["preferredUsername"],
@@ -181,11 +197,22 @@ class ActorSerializer(jsonld.JsonLdSerializer):
     def save(self, **kwargs):
         d = self.prepare_missing_fields()
         d.update(kwargs)
-        return models.Actor.objects.update_or_create(fid=d["fid"], defaults=d)[0]
+        actor = models.Actor.objects.update_or_create(fid=d["fid"], defaults=d)[0]
+        common_utils.attach_content(
+            actor, "summary_obj", self.validated_data["summary"]
+        )
+        return actor
 
-    def validate_summary(self, value):
-        if value:
-            return value[:500]
+    def validate(self, data):
+        validated_data = super().validate(data)
+        if "summary" in data:
+            validated_data["summary"] = {
+                "content_type": "text/html",
+                "text": data["summary"],
+            }
+        else:
+            validated_data["summary"] = None
+        return validated_data
 
 
 class APIActorSerializer(serializers.ModelSerializer):
@@ -828,18 +855,6 @@ def include_image(repr, attachment):
         repr["image"] = None
 
 
-class TruncatedCharField(serializers.CharField):
-    def __init__(self, *args, **kwargs):
-        self.truncate_length = kwargs.pop("truncate_length")
-        super().__init__(*args, **kwargs)
-
-    def to_internal_value(self, v):
-        v = super().to_internal_value(v)
-        if v:
-            v = v[: self.truncate_length]
-        return v
-
-
 class MusicEntitySerializer(jsonld.JsonLdSerializer):
     id = serializers.URLField(max_length=500)
     published = serializers.DateTimeField()
diff --git a/api/funkwhale_api/users/serializers.py b/api/funkwhale_api/users/serializers.py
index cddc2e82aea82859b0728d30777142f6349fec9b..0d167b1a1b7fd8c181f1c3abd587973bb89af62c 100644
--- a/api/funkwhale_api/users/serializers.py
+++ b/api/funkwhale_api/users/serializers.py
@@ -11,6 +11,7 @@ from versatileimagefield.serializers import VersatileImageFieldSerializer
 
 from funkwhale_api.activity import serializers as activity_serializers
 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
 from . import adapters
 from . import models
@@ -27,6 +28,7 @@ class ASCIIUsernameValidator(validators.RegexValidator):
 
 
 username_validators = [ASCIIUsernameValidator()]
+NOOP = object()
 
 
 class RegisterSerializer(RS):
@@ -106,6 +108,7 @@ class UserBasicSerializer(serializers.ModelSerializer):
 
 class UserWriteSerializer(serializers.ModelSerializer):
     avatar = avatar_field
+    summary = common_serializers.ContentSerializer(required=False, allow_null=True)
 
     class Meta:
         model = models.User
@@ -115,8 +118,20 @@ class UserWriteSerializer(serializers.ModelSerializer):
             "avatar",
             "instance_support_message_display_date",
             "funkwhale_support_message_display_date",
+            "summary",
         ]
 
+    def update(self, obj, validated_data):
+        if not obj.actor:
+            obj.create_actor()
+        summary = validated_data.pop("summary", NOOP)
+        obj = super().update(obj, validated_data)
+
+        if summary != NOOP:
+            common_utils.attach_content(obj.actor, "summary_obj", summary)
+
+        return obj
+
 
 class UserReadSerializer(serializers.ModelSerializer):
 
@@ -150,17 +165,24 @@ class UserReadSerializer(serializers.ModelSerializer):
 
 class MeSerializer(UserReadSerializer):
     quota_status = serializers.SerializerMethodField()
+    summary = serializers.SerializerMethodField()
 
     class Meta(UserReadSerializer.Meta):
         fields = UserReadSerializer.Meta.fields + [
             "quota_status",
             "instance_support_message_display_date",
             "funkwhale_support_message_display_date",
+            "summary",
         ]
 
     def get_quota_status(self, o):
         return o.get_quota_status() if o.actor else 0
 
+    def get_summary(self, o):
+        if not o.actor or not o.actor.summary_obj:
+            return
+        return common_serializers.ContentSerializer(o.actor.summary_obj).data
+
 
 class PasswordResetSerializer(PRS):
     def get_email_options(self):
diff --git a/api/tests/federation/test_serializers.py b/api/tests/federation/test_serializers.py
index 4f31dcbdfb4fc714bae53d584b61165563d56bba..93fd14ba17a928a57378c3332733c58514313dfe 100644
--- a/api/tests/federation/test_serializers.py
+++ b/api/tests/federation/test_serializers.py
@@ -53,7 +53,8 @@ def test_actor_serializer_from_ap(db):
     assert actor.type == "Person"
     assert actor.preferred_username == payload["preferredUsername"]
     assert actor.name == payload["name"]
-    assert actor.summary == payload["summary"]
+    assert actor.summary_obj.text == payload["summary"]
+    assert actor.summary_obj.content_type == "text/html"
     assert actor.fid == actor_url
     assert actor.manually_approves_followers is True
     assert actor.private_key is None
@@ -89,7 +90,7 @@ def test_actor_serializer_only_mandatory_field_from_ap(db):
     assert actor.manually_approves_followers is None
 
 
-def test_actor_serializer_to_ap():
+def test_actor_serializer_to_ap(db):
     expected = {
         "@context": jsonld.get_default_context(),
         "id": "https://test.federation/user",
@@ -100,7 +101,6 @@ def test_actor_serializer_to_ap():
         "outbox": "https://test.federation/user/outbox",
         "preferredUsername": "user",
         "name": "Real User",
-        "summary": "Hello world",
         "manuallyApprovesFollowers": False,
         "publicKey": {
             "id": "https://test.federation/user#main-key",
@@ -109,7 +109,7 @@ def test_actor_serializer_to_ap():
         },
         "endpoints": {"sharedInbox": "https://test.federation/inbox"},
     }
-    ac = models.Actor(
+    ac = models.Actor.objects.create(
         fid=expected["id"],
         inbox_url=expected["inbox"],
         outbox_url=expected["outbox"],
@@ -119,11 +119,15 @@ def test_actor_serializer_to_ap():
         public_key=expected["publicKey"]["publicKeyPem"],
         preferred_username=expected["preferredUsername"],
         name=expected["name"],
-        domain=models.Domain(pk="test.federation"),
-        summary=expected["summary"],
+        domain=models.Domain.objects.create(pk="test.federation"),
         type="Person",
         manually_approves_followers=False,
     )
+
+    content = common_utils.attach_content(
+        ac, "summary_obj", {"text": "hello world", "content_type": "text/markdown"}
+    )
+    expected["summary"] = content.rendered
     serializer = serializers.ActorSerializer(ac)
 
     assert serializer.data == expected
@@ -1127,14 +1131,17 @@ def test_local_actor_serializer_to_ap(factories):
         preferred_username=expected["preferredUsername"],
         name=expected["name"],
         domain=models.Domain.objects.create(pk="test.federation"),
-        summary=expected["summary"],
         type="Person",
         manually_approves_followers=False,
     )
+    content = common_utils.attach_content(
+        ac, "summary_obj", {"text": "hello world", "content_type": "text/markdown"}
+    )
     user = factories["users.User"]()
     user.actor = ac
     user.save()
     ac.refresh_from_db()
+    expected["summary"] = content.rendered
     expected["icon"] = {
         "type": "Image",
         "mediaType": "image/jpeg",
diff --git a/api/tests/users/test_views.py b/api/tests/users/test_views.py
index 8156c84b50f33c95a512d09daad8d531dcdb67de..d01252621570aff72de0510d2cce051fb5ea1fc5 100644
--- a/api/tests/users/test_views.py
+++ b/api/tests/users/test_views.py
@@ -1,6 +1,8 @@
 import pytest
 from django.urls import reverse
 
+from funkwhale_api.common import serializers as common_serializers
+from funkwhale_api.common import utils as common_utils
 from funkwhale_api.users.models import User
 
 
@@ -105,7 +107,10 @@ def test_can_fetch_data_from_api(api_client, factories):
     # login required
     assert response.status_code == 401
 
-    user = factories["users.User"](permission_library=True)
+    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)
+
     api_client.login(username=user.username, password="test")
     response = api_client.get(url)
     assert response.status_code == 200
@@ -115,6 +120,10 @@ 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["summary"]
+        == common_serializers.ContentSerializer(summary_obj).data
+    )
 
 
 def test_can_get_token_via_api(api_client, factories):
@@ -202,6 +211,20 @@ def test_user_can_patch_his_own_settings(logged_in_api_client):
     assert user.privacy_level == "me"
 
 
+def test_user_can_patch_description(logged_in_api_client):
+    user = logged_in_api_client.user
+    payload = {"summary": {"content_type": "text/markdown", "text": "hello"}}
+    url = reverse("api:v1:users:users-detail", kwargs={"username": user.username})
+
+    response = logged_in_api_client.patch(url, payload, format="json")
+
+    assert response.status_code == 200
+    user.refresh_from_db()
+
+    assert user.actor.summary_obj.content_type == payload["summary"]["content_type"]
+    assert user.actor.summary_obj.text == payload["summary"]["text"]
+
+
 def test_user_can_request_new_subsonic_token(logged_in_api_client):
     user = logged_in_api_client.user
     user.subsonic_api_token = "test"