diff --git a/api/config/settings/common.py b/api/config/settings/common.py
index 360346b276c5fe769258da96871c559243ede85f..08c9536f22df32c16bb81c6e835b208bcfa5ed76 100644
--- a/api/config/settings/common.py
+++ b/api/config/settings/common.py
@@ -716,3 +716,6 @@ ACTOR_KEY_ROTATION_DELAY = env.int("ACTOR_KEY_ROTATION_DELAY", default=3600 * 48
 SUBSONIC_DEFAULT_TRANSCODING_FORMAT = (
     env("SUBSONIC_DEFAULT_TRANSCODING_FORMAT", default="mp3") or None
 )
+
+# extra tags will be ignored
+TAGS_MAX_BY_OBJ = env.int("TAGS_MAX_BY_OBJ", default=30)
diff --git a/api/funkwhale_api/tags/models.py b/api/funkwhale_api/tags/models.py
index 2905f3d8dc621d8f10a0de924340376b5da61ff7..a5f3a37359888232c72124b5e0ce3ea55981cb75 100644
--- a/api/funkwhale_api/tags/models.py
+++ b/api/funkwhale_api/tags/models.py
@@ -1,5 +1,6 @@
 import re
 
+from django.conf import settings
 from django.contrib.contenttypes.fields import GenericForeignKey
 from django.contrib.contenttypes.models import ContentType
 from django.contrib.postgres.fields import CICharField
@@ -65,6 +66,9 @@ def add_tags(obj, *tags):
 
 @transaction.atomic
 def set_tags(obj, *tags):
+    # we ignore any extra tags if the length of the list is higher
+    # than our accepted size
+    tags = tags[: settings.TAGS_MAX_BY_OBJ]
     tags = set(tags)
     existing = set(
         TaggedItem.objects.for_content_object(obj).values_list("tag__name", flat=True)
diff --git a/api/funkwhale_api/tags/serializers.py b/api/funkwhale_api/tags/serializers.py
index 1f53ffdbdd4ebb56344e5108560fbae58c855db6..9b7b31f503cd3b13fef4cc243944f92fe118a85f 100644
--- a/api/funkwhale_api/tags/serializers.py
+++ b/api/funkwhale_api/tags/serializers.py
@@ -1,5 +1,7 @@
 from rest_framework import serializers
 
+from django.conf import settings
+
 from . import models
 
 
@@ -15,3 +17,18 @@ class TagNameField(serializers.CharField):
         if not models.TAG_REGEX.match(value):
             raise serializers.ValidationError('Invalid tag "{}"'.format(value))
         return value
+
+
+class TagsListField(serializers.ListField):
+    def __init__(self, *args, **kwargs):
+        kwargs.setdefault("min_length", 0)
+        kwargs.setdefault("child", TagNameField())
+        super().__init__(*args, **kwargs)
+
+    def to_internal_value(self, value):
+        value = super().to_internal_value(value)
+        if not value:
+            return value
+        # we ignore any extra tags if the length of the list is higher
+        # than our accepted size
+        return value[: settings.TAGS_MAX_BY_OBJ]
diff --git a/api/tests/tags/test_models.py b/api/tests/tags/test_models.py
index 2bc451a1145054758807e746ec9247f5f6a146ba..b59b28738b4ac557d305779d0aad931e204056fc 100644
--- a/api/tests/tags/test_models.py
+++ b/api/tests/tags/test_models.py
@@ -53,6 +53,24 @@ def test_set_tags(factories, existing, given, expected):
         assert match.content_object == obj
 
 
+@pytest.mark.parametrize(
+    "max, tags, expected",
+    [
+        (5, ["hello", "world"], ["hello", "world"]),
+        # we truncate extra tags
+        (1, ["hello", "world"], ["hello"]),
+        (2, ["hello", "world", "foo"], ["hello", "world"]),
+    ],
+)
+def test_set_tags_honor_TAGS_MAX_BY_OBJ(factories, max, tags, expected, settings):
+    settings.TAGS_MAX_BY_OBJ = max
+    obj = factories["music.Artist"]()
+
+    models.set_tags(obj, *tags)
+
+    assert sorted(obj.tagged_items.values_list("tag__name", flat=True)) == expected
+
+
 @pytest.mark.parametrize("factory_name", ["music.Track", "music.Album", "music.Artist"])
 def test_models_that_support_tags(factories, factory_name):
     tags = ["tag1", "tag2"]
diff --git a/api/tests/tags/test_serializers.py b/api/tests/tags/test_serializers.py
index f9c78a87b24001d8ebea1005013e3c7e1aeeadde..f962e93e43125b81923587e12255a16b8a68eb1f 100644
--- a/api/tests/tags/test_serializers.py
+++ b/api/tests/tags/test_serializers.py
@@ -29,3 +29,18 @@ def test_tag_name_field_validation(name):
     field = serializers.TagNameField()
     with pytest.raises(serializers.serializers.ValidationError):
         field.to_internal_value(name)
+
+
+@pytest.mark.parametrize(
+    "max, tags, expected",
+    [
+        (5, ["hello", "world"], ["hello", "world"]),
+        # we truncate extra tags
+        (1, ["hello", "world"], ["hello"]),
+        (2, ["hello", "world", "foo"], ["hello", "world"]),
+    ],
+)
+def test_tags_list_field_honor_TAGS_MAX_BY_OBJ(max, tags, expected, settings):
+    settings.TAGS_MAX_BY_OBJ = max
+    field = serializers.TagsListField()
+    assert field.to_internal_value(tags) == expected