diff --git a/inc/lektor/commands.h b/inc/lektor/commands.h
index 34201ae7f36b9bc65a53827a0e52e73b532cdb1d..9e3424a40660e2f87f57b19ea7b1c50b21d24ae9 100644
--- a/inc/lektor/commands.h
+++ b/inc/lektor/commands.h
@@ -47,6 +47,8 @@ bool command_plt_rename(struct lkt_state *srv, char *args[LKT_MESSAGE_ARGS_MAX])
 bool command_plt_export(struct lkt_state *srv, char *args[LKT_MESSAGE_ARGS_MAX]);
 bool command_plt_import(struct lkt_state *srv, char *args[LKT_MESSAGE_ARGS_MAX]);
 bool command_plt_add   (struct lkt_state *srv, char *args[LKT_MESSAGE_ARGS_MAX]);
+bool command_plt_list  (struct lkt_state *srv, size_t c, long cont);
+bool command_plt_ctx   (struct lkt_state *srv, size_t c, char *args[LKT_MESSAGE_ARGS_MAX], long cont);
 
 /* The help */
 bool command_help(struct lkt_state *srv, size_t c);
diff --git a/inc/lektor/database.h b/inc/lektor/database.h
index 3c46b47351981ce1ebb791bad495a58f1d1e9dc1..daae958341105a4897375ec7a9ba47e4cd8b809a 100644
--- a/inc/lektor/database.h
+++ b/inc/lektor/database.h
@@ -90,6 +90,7 @@ struct lkt_search {
         lkt_search_playlist,
         lkt_search_queue,
         lkt_search_sticker,
+        lkt_search_listplaylist,
     } type;
 
     void (*call)(void);     /* Called during the iter phase, casted     */
@@ -112,17 +113,19 @@ struct lkt_search {
     struct lkt_uri ka_uri;      /* Search by uri                        */
 };
 
-typedef bool (*lkt_search_init_add_func)(volatile sqlite3 *, struct lkt_uri *, int);
-typedef bool (*lkt_search_database_func)(struct lkt_state *srv, size_t c, int id, int id_len, const char *row);
-typedef bool (*lkt_search_queue_func)   (struct lkt_state *srv, size_t c, int id, int id_len, const char *row);
-typedef bool (*lkt_search_sticker_func) (struct lkt_state *srv, size_t c, const char *sticker, const char *type, int uri, int value);
-
-bool database_search_database_init(volatile sqlite3 *db, struct lkt_search *ret);
-bool database_search_queue_init   (volatile sqlite3 *db, struct lkt_search *ret);
-bool database_search_sticker_init (volatile sqlite3 *db, struct lkt_search *ret);
-bool database_search_playlist_init(volatile sqlite3 *db, struct lkt_search *ret);
+typedef bool (*lkt_search_init_add_func)    (volatile sqlite3 *, struct lkt_uri *, int);
+typedef bool (*lkt_search_database_func)    (struct lkt_state *srv, size_t c, int id, int id_len, const char *row);
+typedef bool (*lkt_search_queue_func)       (struct lkt_state *srv, size_t c, int id, int id_len, const char *row);
+typedef bool (*lkt_search_sticker_func)     (struct lkt_state *srv, size_t c, const char *sticker, const char *type, int uri, int value);
+typedef bool (*lkt_search_listplaylist_func)(struct lkt_state *srv, size_t c, const char *name);
+
+bool database_search_database_init    (volatile sqlite3 *db, struct lkt_search *ret);
+bool database_search_queue_init       (volatile sqlite3 *db, struct lkt_search *ret);
+bool database_search_sticker_init     (volatile sqlite3 *db, struct lkt_search *ret);
+bool database_search_playlist_init    (volatile sqlite3 *db, struct lkt_search *ret);
+bool database_search_listplaylist_init(volatile sqlite3 *db, struct lkt_search *ret);
 bool database_search_iter(struct lkt_search *item);
-bool database_kara_by_id(volatile sqlite3 *db, int id, struct kara_metadata *kara, char filepath[PATH_MAX]);
+bool database_kara_by_id (volatile sqlite3 *db, int id, struct kara_metadata *kara, char filepath[PATH_MAX]);
 
 /* Next and prev operation on the queue. */
 bool database_queue_next(volatile sqlite3 *db, char filepath[PATH_MAX]);
diff --git a/inc/lektor/uri.h b/inc/lektor/uri.h
index f18e476272de2bdb261a678c198f09dbf0c0f3f5..ca506f69d3813fcc050a0785dc71970b94515da8 100644
--- a/inc/lektor/uri.h
+++ b/inc/lektor/uri.h
@@ -5,6 +5,7 @@
 #include <stdbool.h>
 
 enum lkt_uri_type {
+    uri_null,
     uri_id,
     uri_playlist,
     uri_type,
diff --git a/src/commands.c b/src/commands.c
index 54c7e5b8f45db415cda28b1f05cbbdeb72f50eb3..383e0a98b6860870fd06692ae5884b48ec6a685c 100644
--- a/src/commands.c
+++ b/src/commands.c
@@ -433,6 +433,15 @@ lkt_callback_send_row_v2(struct lkt_state *srv, size_t c, int id, int id_len, co
     return true;
 }
 
+bool
+lkt_callback_send_list_plts(struct lkt_state *srv, size_t c, const char *plt_name)
+{
+    struct lkt_message *out = lkt_message_new();
+    out->data_len = safe_snprintf(out->data, LKT_MESSAGE_MAX, "%s\n", plt_name);
+    lkt_state_send(srv, c, out);
+    return true;
+}
+
 static bool
 command_findid(struct lkt_state *srv, size_t c, char *id_str)
 {
@@ -470,6 +479,20 @@ command_findid(struct lkt_state *srv, size_t c, char *id_str)
     return true;
 }
 
+static inline bool
+__iter_search(struct lkt_search *search)
+{
+    int count;
+    for (count = 0; database_search_iter(search); ++count)
+        continue;
+
+    if (count)
+        lkt_set_continuation(search->srv, search->c, search->continuation + count);
+    else
+        LOG_WARN("COMMAND", "%s", "Nothing found");
+    return true;
+}
+
 bool
 command_find(struct lkt_state *srv, size_t c, char *args[LKT_MESSAGE_ARGS_MAX],
              long continuation, bool(*init)(volatile sqlite3 *, struct lkt_search *))
@@ -502,15 +525,38 @@ command_find(struct lkt_state *srv, size_t c, char *args[LKT_MESSAGE_ARGS_MAX],
     }
 
     RETURN_UNLESS(init(srv->db, &search), "Failed to init search", false);
-    for (count = 0; database_search_iter(&search); ++count)
-        continue;
+    return __iter_search(&search);
+}
 
-    if (count)
-        lkt_set_continuation(srv, c, continuation + count);
-    else
-        LOG_WARN("COMMAND", "%s", "Nothing found");
-    lkt_uri_free(&search.ka_uri);
-    return true;
+bool
+command_plt_list(struct lkt_state *srv, size_t c, long cont)
+{
+    struct lkt_search search = {
+        .srv          = srv,
+        .c            = c,
+        .continuation = cont,
+        .msg_count    = lkt_remaining_msg(srv, c) - 3, /* Slots for OK/ACK and continuation */
+        .call         = (void(*)(void)) lkt_callback_send_list_plts,
+    };
+
+    RETURN_UNLESS(database_search_listplaylist_init(srv->db, &search), "Failed to init search", false);
+    return __iter_search(&search);
+}
+
+bool command_plt_ctx(struct lkt_state *srv, size_t c, char *args[LKT_MESSAGE_ARGS_MAX], long cont)
+{
+    struct lkt_search search = {
+        .srv          = srv,
+        .c            = c,
+        .continuation = cont,
+        .msg_count    = lkt_remaining_msg(srv, c) - 3,
+        .call         = (void(*)(void)) lkt_callback_send_row_v2,
+        .plt_name     = args[0],
+        .ka_uri       = { .type = uri_null },
+    };
+
+    RETURN_UNLESS(database_search_playlist_init(srv->db, &search), "Failed to init search", false);
+    return __iter_search(&search);
 }
 
 bool
diff --git a/src/database/find.c b/src/database/find.c
index 4ec182a564105e5251696e5fdae7945616e1cd8d..92889c0de0be8330f54d3618b3289f6c425f6315 100644
--- a/src/database/find.c
+++ b/src/database/find.c
@@ -48,7 +48,8 @@ error:
 bool
 database_search_playlist_init(volatile sqlite3 *db, struct lkt_search *ret)
 {
-    RETURN_UNLESS(ret, "Exit because return pointer is NULL", false);
+    /* Sql queries definitions */
+    char SQL_STMT[LKT_MAX_SQLITE_STATEMENT];
     static const char *SQL_TEMPLATE =
         "WITH content AS ("
         " SELECT kara.id AS id, string, LENGTH(CAST(kara.id AS TEXT)) AS len"
@@ -59,9 +60,24 @@ database_search_playlist_init(volatile sqlite3 *db, struct lkt_search *ret)
         "           AND %s LIKE ?)"
         "SELECT id, string, (SELECT MAX(len) FROM content)"
         "FROM content LIMIT %d OFFSET %d;";
-    char SQL_STMT[LKT_MAX_SQLITE_STATEMENT];
-    ret->type = lkt_search_playlist;
+    static const char *SQL =
+        "WITH content AS ("
+        " SELECT kara.id AS id, string LENGTH(CAST(kara.id AS TEXT)) AS len"
+        " FROM kara_playlist"
+        " JOIN playlist ON playlist.id = playlist_id"
+        "               AND playlist.name COLLATE NOCASE = ?"
+        " JOIN kara ON kara.id = kara_id)"
+        "SELECT id, string (SELECT MAX(len) FROM content)"
+        "FROM content LIMIT %d OFFSET %d;";
+
+    /* Common part */
+    RETURN_UNLESS(ret, "Exit because return pointer is NULL", false);
+    ret->type   = lkt_search_playlist;
+    ret->db     = db;
+    if (ret->ka_uri.type == uri_null)
+        goto uri_is_null;
 
+    /* Filter with an uri */
     safe_snprintf(SQL_STMT, LKT_MAX_SQLITE_STATEMENT, SQL_TEMPLATE,
                   ret->ka_uri.column_name, ret->msg_count, ret->continuation);
     SQLITE_PREPARE(db, ret->stmt, SQL_STMT, error);
@@ -73,8 +89,30 @@ database_search_playlist_init(volatile sqlite3 *db, struct lkt_search *ret)
         SQLITE_BIND_INT(db, ret->stmt, 2, (int) ret->ka_uri.id, error);
         LOG_DEBUG("DB", "%s", "Uri was a size_t");
     }
-    ret->db = db;
     return true;
+
+    /* Don't filter */
+uri_is_null:
+    safe_snprintf(SQL_STMT, LKT_MAX_SQLITE_STATEMENT, SQL, ret->msg_count, ret->continuation);
+    SQLITE_PREPARE(db, ret->stmt, SQL_STMT, error);
+    SQLITE_BIND_TEXT(db, ret->stmt, 1, ret->plt_name, error);
+    return true;
+
+    /* Error */
+error:
+    sqlite3_finalize(ret->stmt);
+    return false;
+}
+
+bool
+database_search_listplaylist_init(volatile sqlite3 *db, struct lkt_search *ret)
+{
+    static const char *SQL = "SELECT name FROM playlist LIMIT %d OFFSET %d;";
+    char SQL_STMT[LKT_MAX_SQLITE_STATEMENT];
+    safe_snprintf(SQL_STMT, LKT_MAX_SQLITE_STATEMENT, SQL, ret->msg_count, ret->continuation);
+    ret->type   = lkt_search_listplaylist;
+    ret->db     = db;
+    SQLITE_PREPARE(db, ret->stmt, SQL_STMT, error);
 error:
     sqlite3_finalize(ret->stmt);
     return false;
@@ -194,6 +232,10 @@ database_search_iter(struct lkt_search *item)
         type    = sqlite3_column_chars(item->stmt, 3);  /* Type */
         return ((lkt_search_sticker_func) item->call)
                (item->srv, item->c, sql_row, type, id, code);
+    case lkt_search_listplaylist:
+        sql_row = sqlite3_column_chars(item->stmt, 0);  /* Name */
+        return ((lkt_search_listplaylist_func) item->call)
+                (item->srv, item->c, sql_row);
     default:
         LOG_WARN("DB", "Search type %d is not implemented", item->type);
         goto end;
diff --git a/src/net/listen.c b/src/net/listen.c
index 49b3e9771f4cae99fd15023df65a12ed9f7cad10..3916f4ccc4210c3cb4c486a6c105f3d70f9f3ad8 100644
--- a/src/net/listen.c
+++ b/src/net/listen.c
@@ -244,8 +244,12 @@ handle_simple_command(struct lkt_state *srv, size_t c, struct lkt_command cmd)
             err = ! command_plt_import(srv, cmd.args);
         else if (STR_MATCH(cmd.name, "__dump"))
             err = ! command_dump(srv, cmd.args);
-        else if (STR_MATCH(cmd.name, "listplaylist"))
+        else if (STR_MATCH(cmd.name, "listplaylistinfo"))
             err = ! command_find(srv, c, cmd.args, cmd.cont, database_search_playlist_init);
+        else if (STR_MATCH(cmd.name, "listplaylists"))
+            err = ! command_plt_list(srv, c, cmd.cont);
+        else if (STR_MATCH(cmd.name, "listplaylist"))
+            err = ! command_plt_ctx(srv, c, cmd.args, cmd.cont);
 
         else if (STR_MATCH(cmd.name, "random"))
             err = !command_set_playback_option(srv, c, lkt_playback_option_random, cmd.args);