diff --git a/api/funkwhale_api/playlists/serializers.py b/api/funkwhale_api/playlists/serializers.py
index 07a7298a77218c83b4e434eed9b2298e574489ee..ce8f312191ed41475232a73904c1913cc9d6f751 100644
--- a/api/funkwhale_api/playlists/serializers.py
+++ b/api/funkwhale_api/playlists/serializers.py
@@ -1,8 +1,14 @@
+from xml.etree.ElementTree import Element
+
+from django.db.models.fields import CharField, IntegerField
+
 from rest_framework import serializers
 
 from funkwhale_api.federation import serializers as federation_serializers
 from funkwhale_api.music.models import Track
 from funkwhale_api.music.serializers import TrackSerializer
+from funkwhale_api.playlists import utils
+from funkwhale_api.playlists.models import Playlist
 from funkwhale_api.users.serializers import UserBasicSerializer
 
 from . import models
@@ -115,3 +121,33 @@ class PlaylistAddManySerializer(serializers.Serializer):
 
     class Meta:
         fields = "allow_duplicates"
+
+
+class XspfSerializer(serializers.Serializer):
+    title = CharField()
+    playlist_id = IntegerField()
+    class Meta:
+        fields = (
+            "title",
+            "playlist_id",
+        )
+
+    def get_title():
+        return "test"
+
+    def generate_xspf_from_playlist():
+        """
+        This returns a string containing playlist data in xspf format
+        """
+        fw_playlist = Playlist.objects.get(id=playlist_id)
+        plt_tracks = fw_playlist.playlist_tracks.prefetch_related("track")
+        xspf_playlist = Element("playlist")
+        xspf_tracklist = utils.write_xspf_headers(
+            xspf_playlist, fw_playlist.name, str(fw_playlist.creation_date)
+        )
+
+        for plt_track in plt_tracks:
+            track = plt_track.track
+            utils.write_xspf_track_data(track, xspf_tracklist)
+        return utils.prettify(xspf_playlist)
+
diff --git a/api/funkwhale_api/playlists/utils.py b/api/funkwhale_api/playlists/utils.py
new file mode 100644
index 0000000000000000000000000000000000000000..598144627f211c3ad9e9d0ea883f2cea304d687b
--- /dev/null
+++ b/api/funkwhale_api/playlists/utils.py
@@ -0,0 +1,178 @@
+import logging
+import re
+from datetime import datetime
+
+# /!\ The next import have xml vulnerabilities but this shouldn't have security implication in funkwhale
+# since there are only used to generate xspf file.
+from xml.etree.ElementTree import Element, SubElement
+
+from defusedxml import ElementTree as etree
+from defusedxml import minidom
+from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned
+
+from funkwhale_api.music.models import Album, Artist, Track
+
+logger = logging.getLogger(__name__)
+
+
+def clean_namespace_xspf(xspf_file):
+    """
+        This will delete any namaespace found in the xspf file. It will also delete any encoding info.
+        This way xspf file will be compatible with our get_track_id_from_xspf function.
+    """
+    file = open(xspf_file)
+    with file as f:
+        xspf_str = f.read()
+    xspf_data = re.sub('xmlns="http://xspf.org/ns/0/"', "", xspf_str)
+    # This is needed because lxml error : "ValueError: Unicode strings with encoding declaration are
+    # not supported. Please use bytes input or XML fragments without declaration."
+    xspf_data = re.sub("'encoding='.'", "", xspf_data)
+    return xspf_data
+
+
+def album_exist(track, artist_id):
+    try:
+        album = track.find(".//album").text
+    except AttributeError as e:
+        logger.info(
+            f"Couldn't find the following attribute while parsing the xml : {e!r}. No album information."
+        )
+        return
+
+    try:
+        album_id = Album.objects.get(title=album, artist_id=artist_id)
+    except Exception as e:
+        logger.info(f"Error while quering database for album : {e!r}")
+        return
+    except MultipleObjectsReturned as e:
+        album_id = Album.objects.filter(title=album, artist_id=artist_id).first
+        return album_id
+    return album_id
+
+
+def get_track_id_from_xspf(xspf_file):
+    """
+        Return a list of funkwhale tracks id from a xspf file. Tracks not found in database are ignored.
+        Usefull to generate playlist from xspf files.
+    """
+    track_list = []
+    xspf_data_clean = clean_namespace_xspf(xspf_file)
+    tree = etree.fromstring(xspf_data_clean)
+    tracks = tree.findall(".//track")
+    added_track_count = 0
+
+    for track in tracks:
+        track_id = ""
+        # Getting metadata of the xspf file
+        try:
+            artist = track.find(".//creator").text
+            title = track.find(".//title").text
+        except AttributeError as e:
+            logger.info(
+                f"Couldn't find the following attribute while parsing the xml file for artist and title data : {e!r}. \
+                Switching to next track..."
+            )
+            continue
+ 
+        # Finding track id in the db
+        try:
+            artist_id = Artist.objects.get(name=artist)
+        except Exception as e:
+            logger.info(f"Error while quering database : {e!r}. Switching to next track.")
+            continue
+        except MultipleObjectsReturned as e:
+            artist_id = Artist.objects.filter(name=artist).first()
+
+        album_id = album_exist(track, artist_id)
+        if album_id:
+            try:
+                track_id = Track.objects.get(
+                    title=title, artist=artist_id.id, album=album_id.id
+                )
+            except ObjectDoesNotExist as e :
+                logger.info(f"Couldn't find track in the database : {e!r}. Trying without album...")
+            except MultipleObjectsReturned as e:
+                track_id = Track.objects.filter(
+                    title=title, artist=artist_id.id, album=album_id.id
+                ).first()
+
+        else:
+            try:
+                track_id = Track.objects.get(title=title, artist=artist_id.id)
+            except ObjectDoesNotExist as e:
+                logger.info(f"Couldn't find track in the database : {e!r}")
+                continue
+            except MultipleObjectsReturned as e:
+                track_id = Track.objects.filter(title=title, artist=artist_id.id).first()
+
+        if track_id:
+            track_list.append(track_id.id)
+            added_track_count = added_track_count + 1
+
+    logger.info(
+        str(len(tracks))
+        + " tracks where found in xspf file. "
+        + str(added_track_count)
+        + " are gonna be added to playlist."
+    )
+    return track_list
+
+
+def generate_xspf_from_tracks_ids(tracks_ids):
+    """
+        This returns a string containing playlist data in xspf format. It's used for test purposes.
+    """
+    xspf_title = "An automated generated playlist"
+    now = datetime.now()
+    xspf_date = now.strftime("%m/%d/%Y")
+    xspf_playlist = Element("playlist")
+    xspf_tracklist = write_xspf_headers(xspf_playlist, xspf_title, xspf_date)
+
+    for track_id in tracks_ids:
+        try:
+            track = Track.objects.get(id=track_id)
+            write_xspf_track_data(track, xspf_tracklist)
+        except ObjectDoesNotExist as e:
+            logger.info(f"Error while quering database : {e!r}")
+    return prettify(xspf_playlist)
+
+
+def write_xspf_headers(xspf_playlist, xspf_title, xspf_date):
+    """
+        This generate the playlist metadata and return a trackList subelement used to insert each track
+        into the playlist
+    """
+    xspf_playlist.set("version", "1")
+    title_xspf = SubElement(xspf_playlist, "title")
+    title_xspf.text = xspf_title
+    date_xspf = SubElement(xspf_playlist, "date")
+    date_xspf.text = xspf_date
+    trackList_xspf = SubElement(xspf_playlist, "trackList")
+    return trackList_xspf
+
+
+def write_xspf_track_data(track, trackList_xspf):
+    """
+        Insert a track into the trackList subelement of a xspf file
+    """
+    track_xspf = SubElement(trackList_xspf, "track")
+    location_xspf = SubElement(track_xspf, "location")
+    location_xspf.text = "https://" + track.domain_name + track.listen_url
+    title_xspf = SubElement(track_xspf, "title")
+    title_xspf.text = str(track.title)
+    creator_xspf = SubElement(track_xspf, "creator")
+    creator_xspf.text = str(track.artist)
+    if str(track.album) == "[non-album tracks]":
+        return
+    else:
+        album_xspf = SubElement(track_xspf, "album")
+        album_xspf.text = str(track.album)
+
+
+def prettify(elem):
+    """
+        Return a pretty-printed XML string for the Element.
+    """
+    rough_string = etree.tostring(elem, "utf-8")
+    reparsed = minidom.parseString(rough_string)
+    return reparsed.toprettyxml(indent="  ")
diff --git a/api/tests/playlists/test_serializers.py b/api/tests/playlists/test_serializers.py
index b1cae0f3658c87cbbbde2d26effea390ed71caf7..d009d79d343748dba7ad9873e74cad5b98402193 100644
--- a/api/tests/playlists/test_serializers.py
+++ b/api/tests/playlists/test_serializers.py
@@ -1,5 +1,7 @@
+from defusedxml import ElementTree as etree
+
 from funkwhale_api.federation import serializers as federation_serializers
-from funkwhale_api.playlists import serializers
+from funkwhale_api.playlists import models, serializers
 from funkwhale_api.users import serializers as users_serializers
 
 
@@ -60,3 +62,14 @@ def test_playlist_serializer(factories, to_api_date):
     serializer = serializers.PlaylistSerializer(playlist)
 
     assert serializer.data == expected
+
+
+def test_generate_xspf_from_playlist(factories):
+    playlist = factories["playlists.PlaylistTrack"]()
+    playlist_factory = models.Playlist.objects.get()
+    xspf_test = serializers.PlaylistSerializer.generate_xspf_from_playlist(playlist.id)
+    tree = etree.fromstring(xspf_test)
+    track1 = playlist_factory.playlist_tracks.get(id=1)
+    track1_name = track1.track
+    assert playlist_factory.name == tree.findtext("./title")
+    assert track1_name.title == tree.findtext("./trackList/track/title")
diff --git a/api/tests/playlists/test_utils.py b/api/tests/playlists/test_utils.py
new file mode 100644
index 0000000000000000000000000000000000000000..53125fc49edfbfbd0751f6dcf82feefb3603f801
--- /dev/null
+++ b/api/tests/playlists/test_utils.py
@@ -0,0 +1,17 @@
+import os
+
+from funkwhale_api.playlists import utils
+
+
+def test_get_track_id_from_xspf(factories, tmp_path):
+    track1 = factories["music.Track"]()
+    track2 = factories["music.Track"]()
+    tracks_ids = [track1.id, track2.id]
+    xspf_content = utils.generate_xspf_from_tracks_ids(tracks_ids)
+    f = open("test.xspf", "w")
+    f.write(xspf_content)
+    f.close()
+    xspf_file = "test.xspf"
+    expected = [track1.id, track2.id]
+    assert utils.get_track_id_from_xspf(xspf_file) == expected
+    os.remove("test.xspf")