diff --git a/api/funkwhale_api/common/mutations.py b/api/funkwhale_api/common/mutations.py
index 11624e9f629312ce66b35e021a41efddbb683e2f..c3e92c15b1579675a4ce243029ffe8a93614ab63 100644
--- a/api/funkwhale_api/common/mutations.py
+++ b/api/funkwhale_api/common/mutations.py
@@ -114,7 +114,14 @@ class UpdateMutationSerializer(serializers.ModelSerializer, MutationSerializer):
         # to ensure we store ids instead of model instances in our json
         # payload
         for field, attr in self.serialized_relations.items():
-            data[field] = getattr(data[field], attr)
+            try:
+                obj = data[field]
+            except KeyError:
+                continue
+            if obj is None:
+                data[field] = None
+            else:
+                data[field] = getattr(obj, attr)
         return data
 
     def create(self, validated_data):
diff --git a/api/funkwhale_api/common/pagination.py b/api/funkwhale_api/common/pagination.py
index e5068bce209da72523077a0d1dee0b7938eba422..ec7c27dc4f9cd25fc80a02fc47a156fdb42d061b 100644
--- a/api/funkwhale_api/common/pagination.py
+++ b/api/funkwhale_api/common/pagination.py
@@ -1,6 +1,29 @@
-from rest_framework.pagination import PageNumberPagination
+from rest_framework.pagination import PageNumberPagination, _positive_int
 
 
 class FunkwhalePagination(PageNumberPagination):
     page_size_query_param = "page_size"
-    max_page_size = 50
+    default_max_page_size = 50
+    default_page_size = None
+    view = None
+
+    def paginate_queryset(self, queryset, request, view=None):
+        self.view = view
+        return super().paginate_queryset(queryset, request, view)
+
+    def get_page_size(self, request):
+        max_page_size = (
+            getattr(self.view, "max_page_size", 0) or self.default_max_page_size
+        )
+        page_size = getattr(self.view, "default_page_size", 0) or max_page_size
+        if self.page_size_query_param:
+            try:
+                return _positive_int(
+                    request.query_params[self.page_size_query_param],
+                    strict=True,
+                    cutoff=max_page_size,
+                )
+            except (KeyError, ValueError):
+                pass
+
+        return page_size
diff --git a/api/funkwhale_api/music/mutations.py b/api/funkwhale_api/music/mutations.py
index 51efa0ab8cd70f7c241c460937a76b75ee8ab658..4d78b8ea9c086649cd17dbc689a9a562e36c4bbb 100644
--- a/api/funkwhale_api/music/mutations.py
+++ b/api/funkwhale_api/music/mutations.py
@@ -21,4 +21,4 @@ class TrackMutationSerializer(mutations.UpdateMutationSerializer):
 
     class Meta:
         model = models.Track
-        fields = ["license", "title", "position"]
+        fields = ["license", "title", "position", "copyright"]
diff --git a/api/funkwhale_api/music/views.py b/api/funkwhale_api/music/views.py
index b5242eeb1c1e22a35c65c6390a0987a853e21e9d..2f0e67cb9eda8bd8d0ad286a1953cd1fb444e43a 100644
--- a/api/funkwhale_api/music/views.py
+++ b/api/funkwhale_api/music/views.py
@@ -524,6 +524,7 @@ class LicenseViewSet(viewsets.ReadOnlyModelViewSet):
     serializer_class = serializers.LicenseSerializer
     queryset = models.License.objects.all().order_by("code")
     lookup_value_regex = ".*"
+    max_page_size = 1000
 
     def get_queryset(self):
         # ensure our licenses are up to date in DB
diff --git a/api/tests/common/test_pagination.py b/api/tests/common/test_pagination.py
new file mode 100644
index 0000000000000000000000000000000000000000..cacbe740c7ddae374ae502ca1ae1a2867edab27b
--- /dev/null
+++ b/api/tests/common/test_pagination.py
@@ -0,0 +1,29 @@
+import pytest
+
+from funkwhale_api.common import pagination
+
+
+@pytest.mark.parametrize(
+    "view_max_page_size, view_default_page_size, request_page_size, expected",
+    [
+        (50, 50, None, 50),
+        (50, 25, None, 25),
+        (25, None, None, 25),
+        (50, 25, 100, 50),
+        (50, None, 100, 50),
+        (50, 25, 33, 33),
+    ],
+)
+def test_funkwhale_pagination_uses_view_page_size(
+    view_max_page_size, view_default_page_size, request_page_size, expected, mocker
+):
+    p = pagination.FunkwhalePagination()
+
+    p.view = mocker.Mock(
+        max_page_size=view_max_page_size, default_page_size=view_default_page_size
+    )
+    query = {}
+    if request_page_size:
+        query["page_size"] = request_page_size
+    request = mocker.Mock(query_params=query)
+    assert p.get_page_size(request) == expected
diff --git a/api/tests/music/test_mutations.py b/api/tests/music/test_mutations.py
index d6b8223d4efe9397710227028703ca6573155983..bc9e81f8e3e79f3da52c6a1caad11e831cf560e0 100644
--- a/api/tests/music/test_mutations.py
+++ b/api/tests/music/test_mutations.py
@@ -13,6 +13,18 @@ def test_track_license_mutation(factories, now):
     assert track.license.code == "cc-by-sa-4.0"
 
 
+def test_track_null_license_mutation(factories, now):
+    track = factories["music.Track"](license="cc-by-sa-4.0")
+    mutation = factories["common.Mutation"](
+        type="update", target=track, payload={"license": None}
+    )
+    licenses.load(licenses.LICENSES)
+    mutation.apply()
+    track.refresh_from_db()
+
+    assert track.license is None
+
+
 def test_track_title_mutation(factories, now):
     track = factories["music.Track"](title="foo")
     mutation = factories["common.Mutation"](
@@ -24,6 +36,17 @@ def test_track_title_mutation(factories, now):
     assert track.title == "bar"
 
 
+def test_track_copyright_mutation(factories, now):
+    track = factories["music.Track"](copyright="foo")
+    mutation = factories["common.Mutation"](
+        type="update", target=track, payload={"copyright": "bar"}
+    )
+    mutation.apply()
+    track.refresh_from_db()
+
+    assert track.copyright == "bar"
+
+
 def test_track_position_mutation(factories):
     track = factories["music.Track"](position=4)
     mutation = factories["common.Mutation"](
diff --git a/api/tests/music/test_views.py b/api/tests/music/test_views.py
index 7b12c6c8ffe37c5efa003007a5f3b2a7b45339a3..2c3a61c054b732f2234b51491a31baacb4980fab 100644
--- a/api/tests/music/test_views.py
+++ b/api/tests/music/test_views.py
@@ -612,7 +612,7 @@ def test_list_licenses(api_client, preferences, mocker):
 
     expected = [
         serializers.LicenseSerializer(l.conf).data
-        for l in models.License.objects.order_by("code")[:25]
+        for l in models.License.objects.order_by("code")
     ]
     url = reverse("api:v1:licenses-list")
 
diff --git a/front/src/components/library/EditForm.vue b/front/src/components/library/EditForm.vue
index 0001081192048274ac2c8d3d55f5f809aa29093c..a2df96c0018dcf0d2f9bf5dc461be7cf872632c1 100644
--- a/front/src/components/library/EditForm.vue
+++ b/front/src/components/library/EditForm.vue
@@ -59,10 +59,28 @@
           <label :for="fieldConfig.id">{{ fieldConfig.label }}</label>
           <input :type="fieldConfig.inputType || 'text'" v-model="values[fieldConfig.id]" :required="fieldConfig.required" :name="fieldConfig.id" :id="fieldConfig.id">
         </template>
+        <template v-else-if="fieldConfig.type === 'license'">
+          <label :for="fieldConfig.id">{{ fieldConfig.label }}</label>
+
+          <select
+            ref="license"
+            v-model="values[fieldConfig.id]"
+            :required="fieldConfig.required"
+            :id="fieldConfig.id"
+            class="ui fluid search dropdown">
+              <option :value="null"><translate translate-context="*/*/*">N/A</translate></option>
+              <option v-for="license in licenses" :key="license.code" :value="license.code">{{ license.name}}</option>
+          </select>
+          <button class="ui tiny basic left floated button" form="noop" @click.prevent="values[fieldConfig.id] = null">
+            <i class="x icon"></i>
+            <translate translate-context="Content/Library/Button.Label">Clear</translate>
+          </button>
+
+        </template>
         <div v-if="values[fieldConfig.id] != initialValues[fieldConfig.id]">
           <button class="ui tiny basic right floated reset button" form="noop" @click.prevent="values[fieldConfig.id] = initialValues[fieldConfig.id]">
             <i class="undo icon"></i>
-            <translate translate-context="Content/Library/Button.Label" :translate-params="{value: initialValues[fieldConfig.id]}">Reset to initial value: %{ value }</translate>
+            <translate translate-context="Content/Library/Button.Label" :translate-params="{value: initialValues[fieldConfig.id] || ''}">Reset to initial value: %{ value }</translate>
           </button>
         </div>
       </div>
@@ -87,6 +105,7 @@
 </template>
 
 <script>
+import $ from 'jquery'
 import _ from '@/lodash'
 import axios from "axios"
 import EditList from '@/components/library/EditList'
@@ -94,7 +113,7 @@ import EditCard from '@/components/library/EditCard'
 import edits from '@/edits'
 
 export default {
-  props: ["objectType", "object"],
+  props: ["objectType", "object", "licenses"],
   components: {
     EditList,
     EditCard
@@ -113,6 +132,9 @@ export default {
   created () {
     this.setValues()
   },
+  mounted() {
+    $(".ui.dropdown").dropdown({fullTextSearch: true})
+  },
   computed: {
     configs: edits.getConfigs,
     config: edits.getConfig,
@@ -182,6 +204,15 @@ export default {
         }
       )
     }
+  },
+  watch: {
+    'values.license' (newValue) {
+      if (newValue === null) {
+        $(this.$refs.license).dropdown('clear')
+      } else {
+        $(this.$refs.license).dropdown('set selected', newValue)
+      }
+    }
   }
 }
 </script>
diff --git a/front/src/components/library/TrackEdit.vue b/front/src/components/library/TrackEdit.vue
index 7e26d1df10595cbb229f68b58350c715431c6c27..945bae961029f80a29fa878baaefd9d2c91ea8e4 100644
--- a/front/src/components/library/TrackEdit.vue
+++ b/front/src/components/library/TrackEdit.vue
@@ -6,9 +6,17 @@
         <translate v-if="canEdit" key="1" translate-context="Content/*/Title">Edit this track</translate>
         <translate v-else key="2" translate-context="Content/*/Title">Suggest an edit on this track</translate>
       </h2>
-      <edit-form :object-type="objectType" :object="object" :can-edit="canEdit"></edit-form>
+      <edit-form
+        v-if="!isLoadingLicenses"
+        :object-type="objectType"
+        :object="object"
+        :can-edit="canEdit"
+        :licenses="licenses"></edit-form>
+      <div v-else class="ui inverted active dimmer">
+        <div class="ui loader"></div>
       </div>
-    </section>
+    </div>
+  </section>
 </template>
 
 <script>
@@ -19,12 +27,27 @@ export default {
   props: ["objectType", "object", "libraries"],
   data() {
     return {
-      id: this.object.id
+      id: this.object.id,
+      isLoadingLicenses: false,
+      licenses: []
     }
   },
   components: {
     EditForm
   },
+  created () {
+    this.fetchLicenses()
+  },
+  methods: {
+    fetchLicenses () {
+      let self = this
+      self.isLoadingLicenses = true
+      axios.get('licenses/').then((response) => {
+        self.isLoadingLicenses = false
+        self.licenses = response.data.results
+      })
+    }
+  },
   computed: {
     canEdit () {
       return true
diff --git a/front/src/edits.js b/front/src/edits.js
index ccc9c16b39ee27a357ba5f6545228fc1be900697..c72cb4b09822bb716a9358e49b046f26141e901b 100644
--- a/front/src/edits.js
+++ b/front/src/edits.js
@@ -10,13 +10,6 @@ export default {
             label: this.$pgettext('Content/Track/*/Noun', 'Title'),
             getValue: (obj) => { return obj.title }
           },
-          {
-            id: 'license',
-            type: 'text',
-            required: false,
-            label: this.$pgettext('Content/*/*/Noun', 'License'),
-            getValue: (obj) => { return obj.license }
-          },
           {
             id: 'position',
             type: 'text',
@@ -24,7 +17,21 @@ export default {
             required: false,
             label: this.$pgettext('*/*/*/Short, Noun', 'Position'),
             getValue: (obj) => { return obj.position }
-          }
+          },
+          {
+            id: 'copyright',
+            type: 'text',
+            required: false,
+            label: this.$pgettext('Content/Track/*/Noun', 'Copyright'),
+            getValue: (obj) => { return obj.copyright }
+          },
+          {
+            id: 'license',
+            type: 'license',
+            required: false,
+            label: this.$pgettext('Content/*/*/Noun', 'License'),
+            getValue: (obj) => { return obj.license },
+          },
         ]
       }
     }