diff --git a/amadeus/i18n/en/amadeus.ftl b/amadeus/i18n/en/amadeus.ftl
index 9c4207ccd3ab6b08b4870590f4da76b6b93dcedd..320a417e599aede5625a6361e24b67bc542d1feb 100644
--- a/amadeus/i18n/en/amadeus.ftl
+++ b/amadeus/i18n/en/amadeus.ftl
@@ -17,6 +17,9 @@ empty-history   = Empty History
 empty-playlists = Empty playlists
 empty-playlist  = Empty playlists { $name }
 
+create-playlist = Create playlist
+delete-playlist = Confirm playlist deletion
+
 next-kara        = Next kara
 prev-kara        = Previous kara
 toggle-playback  = Play/Pause
diff --git a/amadeus/i18n/es-ES/amadeus.ftl b/amadeus/i18n/es-ES/amadeus.ftl
index 91078f9aa6181753e247139d537f09ecef72f074..3caf928503806b48cccacb941f282b2d950ce58b 100644
--- a/amadeus/i18n/es-ES/amadeus.ftl
+++ b/amadeus/i18n/es-ES/amadeus.ftl
@@ -17,6 +17,9 @@ empty-history   = No has escuchado nada recientemente
 empty-playlists = No tienes listas de reproducción
 empty-playlist  = No hay nada en la lista { $name }
 
+create-playlist = Crear lista de reproducción
+delete-playlist = Confirmar la eliminación de la lista de reproducción
+
 next-kara        = Póxima kara
 prev-kara        = Previo kara
 toggle-playback  = Alternar reprod.
diff --git a/amadeus/i18n/fr-FR/amadeus.ftl b/amadeus/i18n/fr-FR/amadeus.ftl
index 9b53636cf2dbc2466aeb332f5b4f3edd0f306c49..64a57b50ca3ef8652e59f197dece26fe4408a71d 100644
--- a/amadeus/i18n/fr-FR/amadeus.ftl
+++ b/amadeus/i18n/fr-FR/amadeus.ftl
@@ -17,6 +17,9 @@ empty-history   = L'historique est vide
 empty-playlists = Il n'y a pas de listes de lecture de disponibles
 empty-playlist  = La liste de lecture { $name } est vide
 
+create-playlist = Création de liste de lecture
+delete-playlist = Supprimer la liste de lecture
+
 next-kara        = Kara suivant
 prev-kara        = Kara précédent
 toggle-playback  = Play/Pause
diff --git a/amadeus/src/app.rs b/amadeus/src/app.rs
index e40eda136e841aa5c6c7fd8457445739c810f1ed..91779f29650894fda0f48029f98824bdd8366339 100644
--- a/amadeus/src/app.rs
+++ b/amadeus/src/app.rs
@@ -23,15 +23,12 @@ use crate::{
     store::Store,
 };
 use cosmic::{
-    app::{context_drawer, Core, Task},
+    app::{context_drawer, Core, Message as CosmicMessage, Task},
     iced::{Length, Subscription},
     prelude::*,
-    style, theme, widget, Application,
-};
-use futures::{
-    prelude::*,
-    stream::{self, FuturesUnordered},
+    style, task, theme, widget, Application,
 };
+use futures::{prelude::*, stream};
 use lektor_lib::*;
 use lektor_payloads::{
     Epochs, KId, Kara, PlaylistInfo, Priority, SearchFrom, PRIORITY_LENGTH, PRIORITY_VALUES,
@@ -94,6 +91,9 @@ pub struct AppModel {
     tmp_remote_valid: bool,
     tmp_remote_user: String,
     tmp_remote_token: String,
+
+    // Temporary things for the playlist creation
+    tmp_playlist_name: Option<String>,
 }
 
 /// A command to send to the lektord instance.
@@ -129,7 +129,9 @@ pub enum LektordCommand {
     PlaylistDelete(KId),
     PlaylistRemoveKara(KId, KId),
     PlaylistGetContent(KId),
+    PlaylistGetInfos(KId),
     PlaylistShuffleContent(KId),
+    PlaylistCreate,
 
     // Misc stuff
     DownloadKaraInfo(KId),
@@ -171,7 +173,6 @@ pub enum LektordMessage {
     ChangedAvailablePlaylists(Vec<KId>),
     ChangedAvailablePlaylistInfos(PlaylistInfo),
     ChangedPlaylistContent(KId, Vec<KId>),
-    ChangedPlaylistsContent(Vec<(KId, Vec<KId>)>),
 }
 
 /// Messages emitted by the application and its widgets.
@@ -182,9 +183,10 @@ pub enum Message {
     OpenKaraInfo(KId),
     ToggleContextPage(ContextPage),
 
-    // Playlist selection stuff
+    // Playlist selection and creation stuff
     SelectPlaylist(KId),
     UnSelectPlaylist,
+    UpdatePlaylistCreationName(String),
 
     // Update the configuration
     UpdateConfig(ConfigMessage),
@@ -240,6 +242,7 @@ impl Application for AppModel {
             tmp_remote_valid: true,
             tmp_remote_user: config.user().user.clone(),
             tmp_remote_token: config.user().token.clone(),
+            tmp_playlist_name: None,
 
             connect_config: Arc::new(RwLock::new(config.get_connect_config())),
             cosmic_config,
@@ -337,6 +340,8 @@ impl Application for AppModel {
             ContextPage::About => context_pages::about::view(self),
             ContextPage::Settings => context_pages::config::view(self),
             ContextPage::KaraInfo(kid) => context_pages::kara_info::view(self.store.get(kid)),
+            ContextPage::CreatePlaylist => context_pages::playlist_creation::view(self),
+            ContextPage::DeletePlaylist(plt_id) => context_pages::playlist_deletion::view(plt_id),
         })
     }
 
@@ -419,13 +424,13 @@ impl Application for AppModel {
             Message::QueryWithFilters => {
                 let config = self.connect_config.clone();
                 let filters: Vec<_> = self.search_filter.iter_cloned().collect();
-                Task::future(async move {
+                task::future(async move {
                     requests::search_karas(&*config.read().await, SearchFrom::Database, filters)
                         .await
-                        .map(|res| cosmic::app::message::app(Message::QueryWithFiltersResults(res)))
+                        .map(|res| CosmicMessage::App(Message::QueryWithFiltersResults(res)))
                         .unwrap_or_else(|err| {
                             log::error!("failed to query with filters: {err}");
-                            cosmic::app::message::none()
+                            CosmicMessage::None
                         })
                 })
             }
@@ -438,6 +443,10 @@ impl Application for AppModel {
                 self.selected_playlist = None;
                 Task::none()
             }
+            Message::UpdatePlaylistCreationName(name) => {
+                self.tmp_playlist_name = (!name.is_empty()).then_some(name);
+                Task::none()
+            }
         }
     }
 
@@ -464,9 +473,9 @@ impl AppModel {
     fn update_connect_config(&self) -> Task<Message> {
         let config = self.config.get_connect_config();
         let connect_config = self.connect_config.clone();
-        Task::future(async move {
+        task::future(async move {
             *connect_config.write_owned().await = config;
-            cosmic::app::message::none()
+            CosmicMessage::None
         })
     }
 
@@ -615,14 +624,34 @@ impl AppModel {
 
             // Down here, got updates from lektord.
             LektordMessage::ChangedAvailablePlaylists(names) => {
-                let playlists = self.store.keep_playlists(&names);
-                self.update_playlists_content(playlists)
+                let config = self.connect_config.clone();
+
+                let playlists_with_content = stream::iter(self.store.keep_playlists(&names))
+                    .zip(stream::repeat_with(move || config.clone()))
+                    .filter_map(|(id, config)| async move {
+                        requests::get_playlist_content(config.read().await.as_ref(), id)
+                            .await
+                            .map(|content| (id, content))
+                            .ok()
+                    });
+
+                Task::run(playlists_with_content, CosmicMessage::App).then(|msg| match msg {
+                    CosmicMessage::App((id, content)) => Task::batch([
+                        Task::done(CosmicMessage::App(Message::SendCommand(
+                            LektordCommand::PlaylistGetInfos(id),
+                        ))),
+                        Task::done(CosmicMessage::App(Message::LektordUpdate(
+                            LektordMessage::ChangedPlaylistContent(id, content),
+                        ))),
+                    ]),
+                    _ => Task::none(),
+                })
             }
 
             LektordMessage::ChangedAvailablePlaylistInfos(infos) => (self.store)
                 .set_playlist_infos(infos)
                 .map_or_else(Task::none, |plt_id| {
-                    Task::done(cosmic::app::Message::App(Message::SendCommand(
+                    task::message(CosmicMessage::App(Message::SendCommand(
                         LektordCommand::PlaylistGetContent(plt_id),
                     )))
                 }),
@@ -646,51 +675,20 @@ impl AppModel {
 
             LektordMessage::ChangedPlaylistContent(name, plt) => {
                 self.store.set_playlist_content(name, plt);
-                Task::none()
-            }
-            LektordMessage::ChangedPlaylistsContent(changes) => {
-                changes
-                    .into_iter()
-                    .for_each(|(name, plt)| self.store.set_playlist_content(name, plt));
-                Task::none()
+                Task::done(CosmicMessage::App(Message::SendCommand(
+                    LektordCommand::PlaylistGetInfos(name),
+                )))
             }
         }
     }
 
-    /// Update playlists' content.
-    fn update_playlists_content(&mut self, playlists: Vec<KId>) -> Task<Message> {
-        let config = self.connect_config.clone();
-
-        Task::future(async move {
-            let updated_playlists = stream::iter(playlists)
-                .zip(stream::repeat_with(move || config.clone()))
-                .filter_map(|(id, config)| async move {
-                    requests::get_playlist_content(config.read().await.as_ref(), id)
-                        .await
-                        .map(|content| (id, content))
-                        .ok()
-                })
-                .collect::<FuturesUnordered<_>>()
-                .await;
-
-            cosmic::app::Message::App(Message::LektordUpdate(
-                LektordMessage::ChangedPlaylistsContent(Vec::from_iter(updated_playlists)),
-            ))
-        })
-    }
-
     /// Send commands to lektord.
     fn send_command(&mut self, cmd: LektordCommand) -> Task<Message> {
         let config = self.connect_config.clone();
         use lektor_payloads::*;
-        macro_rules! msg {
-            ($msg:ident $(($($args:expr),+))?) => {
-                cosmic::app::message::app(Message::LektordUpdate(LektordMessage::$msg $(($($args),+))?))
-            };
-        }
         macro_rules! cmd {
             ($req:ident ($($arg:expr),*) $(, $res:pat => $handle:expr)?) => {
-                Task::future(async move {
+                task::future(async move {
                     cmd!(@handle $req:
                         requests::$req(config.read().await.as_ref() $(, $arg)*).await,
                         $($res => $handle)?
@@ -698,12 +696,12 @@ impl AppModel {
                 })
             };
 
-            (@handle $txt:ident: $req:expr, ) => { cmd!(@handle $txt: $req, _ => cosmic::app::message::none()) };
+            (@handle $txt:ident: $req:expr, ) => { cmd!(@handle $txt: $req, _ => CosmicMessage::None) };
             (@handle $txt:ident: $req:expr, $res:pat => $handle:expr) => { match $req {
                 Ok($res) => $handle,
                 Err(err) => {
                     log::error!("failed '{}': {err}", stringify!($txt));
-                    return cosmic::app::message::none()
+                    return CosmicMessage::None
                 }
             }};
         }
@@ -720,49 +718,58 @@ impl AppModel {
             LektordCommand::QueueClear => cmd!(remove_range_from_queue(..)),
             LektordCommand::QueueCrop => cmd!(remove_range_from_queue(1..)),
             LektordCommand::QueueGet => cmd!(get_queue_range(..), queue => {
+                let mut counts = <[usize; PRIORITY_LENGTH]>::default();
+                queue.iter().for_each(|(level, _)| counts[level.index()] += 1);
+
                 let mut ret = <[Vec<KId>; PRIORITY_LENGTH]>::default();
+                counts.into_iter().enumerate().for_each(|(idx, count)| ret[idx].reserve(count));
                 queue.into_iter().for_each(|(level, kid)| ret[level.index()].push(kid));
-                msg!(ChangedQueue(ret))
+
+                CosmicMessage::App(Message::LektordUpdate(LektordMessage::ChangedQueue(ret)))
             }),
             LektordCommand::QueueLevelShuffle(lvl) => cmd!(shuffle_level_queue(lvl)),
             LektordCommand::QueueLevelClear(lvl) => cmd!(remove_level_from_queue(lvl)),
             LektordCommand::QueueLevelGet(lvl) => cmd!(get_queue_level(lvl), queue => {
-                msg!(ChangedQueueLevel(lvl, queue))
+                CosmicMessage::App(Message::LektordUpdate(LektordMessage::ChangedQueueLevel(lvl, queue)))
             }),
 
             LektordCommand::DownloadKaraInfo(kid) => cmd!(get_kara_by_kid(kid), kara => {
-                msg!(DownloadedKaraInfo(kara))
+                CosmicMessage::App(Message::LektordUpdate(LektordMessage::DownloadedKaraInfo(kara)))
             }),
             LektordCommand::DownloadKarasInfo(kids) => cmd!(get_karas_by_kid(kids), karas => {
-                msg!(DownloadedKarasInfo(karas))
+                CosmicMessage::App(Message::LektordUpdate(LektordMessage::DownloadedKarasInfo(karas)))
             }),
 
             LektordCommand::HistoryClear => cmd!(remove_range_from_history(..)),
             LektordCommand::HistoryGet => cmd!(get_history_range(..), history => {
-                msg!(ChangedHistory(history))
+                CosmicMessage::App(Message::LektordUpdate(LektordMessage::ChangedHistory(history)))
             }),
 
-            LektordCommand::DatabaseGet => {
-                Task::done(cosmic::app::Message::App(Message::SendCommand(
-                    LektordCommand::DownloadKarasInfo(self.store.take_kara_ids()),
-                )))
-            }
+            LektordCommand::DatabaseGet => task::message(CosmicMessage::App(Message::SendCommand(
+                LektordCommand::DownloadKarasInfo(self.store.take_kara_ids()),
+            ))),
 
             LektordCommand::PlaylistGetContent(id) => cmd!(get_playlist_content(id), content => {
-                msg!(ChangedPlaylistContent(id, content))
+                CosmicMessage::App(Message::LektordUpdate(LektordMessage::ChangedPlaylistContent(id, content)))
             }),
             LektordCommand::PlaylistDelete(plt_id) => cmd!(delete_playlist(plt_id)),
             LektordCommand::PlaylistRemoveKara(plt_id, kid) => {
                 cmd!(remove_from_playlist(plt_id, KaraFilter::KId(kid)))
             }
-            LektordCommand::PlaylistsGet => {
-                cmd!(get_playlists(), playlists => {
-                    let playlists = playlists.into_iter().map(|(id, _)| id).collect();
-                    msg!(ChangedAvailablePlaylists(playlists))
-                })
-            }
+            LektordCommand::PlaylistsGet => cmd!(get_playlists(), playlists => {
+                let playlists = playlists.into_iter().map(|(id, _)| id).collect();
+                CosmicMessage::App(Message::LektordUpdate(LektordMessage::ChangedAvailablePlaylists(playlists)))
+            }),
             LektordCommand::PlaylistShuffleContent(id) => cmd!(shuffle_playlist(id), _ => {
-                cosmic::app::message::app(Message::SendCommand(LektordCommand::PlaylistGetContent(id)))
+                CosmicMessage::App(Message::SendCommand(LektordCommand::PlaylistGetContent(id)))
+            }),
+            LektordCommand::PlaylistCreate => (self.tmp_playlist_name.take()).map_or_else(Task::none, |name| {
+                Task::batch([cmd!(create_playlist(name.as_str()), id => {
+                    CosmicMessage::App(Message::SendCommand(LektordCommand::PlaylistGetInfos(id)))
+                }), Task::done(CosmicMessage::App(Message::ToggleContextPage(ContextPage::CreatePlaylist)))])
+            }),
+            LektordCommand::PlaylistGetInfos(plt_id) => cmd!(get_playlist_info(plt_id), infos => {
+                CosmicMessage::App(Message::LektordUpdate(LektordMessage::ChangedAvailablePlaylistInfos(infos)))
             }),
         }
     }
diff --git a/amadeus/src/app/context_pages.rs b/amadeus/src/app/context_pages.rs
index f74507bc738cbb230c2f8cf5ad0f56d135436cbd..32dc856becf9da21b7e5d5f2d15ad2d50699ee45 100644
--- a/amadeus/src/app/context_pages.rs
+++ b/amadeus/src/app/context_pages.rs
@@ -5,6 +5,8 @@ use lektor_payloads::KId;
 pub mod about;
 pub mod config;
 pub mod kara_info;
+pub mod playlist_creation;
+pub mod playlist_deletion;
 
 /// The context page to display in the context drawer. This is the pane on the right that can be
 /// hidden or shown.
@@ -23,6 +25,14 @@ pub enum ContextPage {
     /// etc...)
     #[display("{}", fl!("kara"))]
     KaraInfo(KId),
+
+    /// Create a playlist, prompts for a name.
+    #[display("{}", fl!("create-playlist"))]
+    CreatePlaylist,
+
+    /// Delete a playlist, prompts for confirmation.
+    #[display("{}", fl!("delete-playlist"))]
+    DeletePlaylist(KId),
 }
 
 impl ContextPage {
diff --git a/amadeus/src/app/context_pages/playlist_creation.rs b/amadeus/src/app/context_pages/playlist_creation.rs
new file mode 100644
index 0000000000000000000000000000000000000000..300b11187c9509e5dfbef4cbb9fcfc2390b58f85
--- /dev/null
+++ b/amadeus/src/app/context_pages/playlist_creation.rs
@@ -0,0 +1,35 @@
+use crate::{
+    app::{context_pages::ContextPage, AppModel, LektordCommand, Message},
+    fl,
+};
+use cosmic::{app::context_drawer, iced::Length, style, widget};
+
+/// View the page for a playlist creation.
+pub fn view(state: &AppModel) -> context_drawer::ContextDrawer<Message> {
+    context_drawer::context_drawer(
+        widget::settings::section()
+            .add(widget::settings::item(
+                "todo: user",
+                state.config.user().user.as_str(),
+            ))
+            .add(
+                widget::text_input(
+                    "todo: place-holder",
+                    state.tmp_playlist_name.as_deref().unwrap_or_default(),
+                )
+                .on_input(Message::UpdatePlaylistCreationName),
+            )
+            .add(
+                widget::button::text("create")
+                    .trailing_icon(widget::icon::from_svg_bytes(crate::icons::EDIT))
+                    .class(style::Button::Suggested)
+                    .width(Length::Fill)
+                    .on_press_maybe(
+                        (state.tmp_playlist_name.is_some())
+                            .then_some(Message::SendCommand(LektordCommand::PlaylistCreate)),
+                    ),
+            ),
+        Message::ToggleContextPage(ContextPage::CreatePlaylist),
+    )
+    .title(fl!("create-playlist"))
+}
diff --git a/amadeus/src/app/context_pages/playlist_deletion.rs b/amadeus/src/app/context_pages/playlist_deletion.rs
new file mode 100644
index 0000000000000000000000000000000000000000..3196d1b7c28ba7c61fe89e0a807c67947a01f9fb
--- /dev/null
+++ b/amadeus/src/app/context_pages/playlist_deletion.rs
@@ -0,0 +1,18 @@
+use crate::{
+    app::{LektordCommand, Message},
+    fl,
+};
+use cosmic::{
+    app::context_drawer,
+    prelude::*,
+    style,
+    widget::{self, tooltip::Position},
+};
+use lektor_payloads::KId;
+
+use super::ContextPage;
+
+/// View the page for a playlist deletion.
+pub fn view<'a>(plt_id: KId) -> context_drawer::ContextDrawer<'a, Message> {
+    todo!()
+}
diff --git a/amadeus/src/app/pages/playlists.rs b/amadeus/src/app/pages/playlists.rs
index cb3975858925a941744788166b1b0c949483aecb..12059718689bfeeafe6a53c4334173a39cf77b60 100644
--- a/amadeus/src/app/pages/playlists.rs
+++ b/amadeus/src/app/pages/playlists.rs
@@ -1,5 +1,6 @@
 use crate::{
     app::{
+        context_pages::ContextPage,
         kard,
         pages::{PageView, PageViewControl},
         LektordCommand, Message,
@@ -24,8 +25,12 @@ fn view_playlist_card(playlist: &Playlist) -> Option<Element<Message>> {
 fn view_playlists_cards(store: &Store) -> Element<Message> {
     PageView::default()
         .titles(fl!("playlists"), fl!("empty-playlists"))
-        .controls([PageViewControl::new(crate::icons::RETRY)
-            .message(Message::SendCommand(LektordCommand::PlaylistsGet))])
+        .controls([
+            PageViewControl::new(crate::icons::PLUS)
+                .message(Message::ToggleContextPage(ContextPage::CreatePlaylist)),
+            PageViewControl::new(crate::icons::RETRY)
+                .message(Message::SendCommand(LektordCommand::PlaylistsGet)),
+        ])
         .push_when(store.iter_playlists().count() != 0, || {
             store
                 .iter_playlists()
@@ -40,7 +45,7 @@ fn view_playlists_cards(store: &Store) -> Element<Message> {
 /// Display the page about a specific playlist.
 fn view_playlist_content(store: &Store, id: KId) -> Element<Message> {
     let Some(infos) = store.playlist(id) else {
-        todo!()
+        todo!("failed to show playlist content")
     };
 
     let name = infos
@@ -96,7 +101,7 @@ fn view_playlist_content(store: &Store, id: KId) -> Element<Message> {
             PageViewControl::new(icons::RETRY)
                 .message(Message::SendCommand(LektordCommand::PlaylistGetContent(id))),
             PageViewControl::new(icons::METEOR)
-                .message(Message::SendCommand(LektordCommand::PlaylistDelete(id)))
+                .message(Message::ToggleContextPage(ContextPage::DeletePlaylist(id)))
                 .destructive(),
         ])
         .push_and_ignore_for_empty(infos)