diff --git a/doc/lkt.1 b/doc/lkt.1
index fd8e1648ea56b3786598cd1dad0fa640639280fc..bf0a7db2f1ff47a6d097074ec744e204bee05b50 100644
--- a/doc/lkt.1
+++ b/doc/lkt.1
@@ -71,6 +71,12 @@ Deletes karas from a playlist with a valid \fIquery\fP
 .TP
 \fBplt add\fP <plt-name> <query>
 Adds karas to a playlist with a valid \fIquery\fP
+.TP
+\fBplt list\fP <plt-name>
+List the content of the playlist named <plt-name>
+.TP
+\fBplt list\fP
+List all the available playlists
 .PP
 
 \fIQUEUE-COMMANDS\fP
@@ -125,6 +131,12 @@ Search and prints the kara that correspond to the query in the database
 Search, prints and add to an existing playlist the karas that match
 the query
 .TP
+\fBsearch plt\fP <plt-name>
+List the content of the playlist named <plt-name>
+.TP
+\fBsearch plt\fP
+List all the available playlists
+.TP
 \fBsearch count\fP <query>
 Search and prints the number of karas that match the query
 .TP
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..413386561ae09d5a38a08f2a2b45da81b10b0126 100644
--- a/src/commands.c
+++ b/src/commands.c
@@ -433,6 +433,16 @@ 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();
+    /* If the playlist is named OK or ACK... */
+    out->data_len = safe_snprintf(out->data, LKT_MESSAGE_MAX, "name: %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 +480,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 +526,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..960a6295e59d2baca98ae7aabefa58a16aa59053 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"
@@ -56,12 +57,28 @@ database_search_playlist_init(volatile sqlite3 *db, struct lkt_search *ret)
         " JOIN playlist ON playlist.id = playlist_id"
         "               AND playlist.name COLLATE nocase = ?"
         " JOIN kara ON kara.id = kara_id"
-        "           AND %s LIKE ?)"
-        "SELECT id, string, (SELECT MAX(len) FROM content)"
+        "           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 */
+    LOG_INFO("DB", "List plt '%s' with non null uri", ret->plt_name);
     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,7 +90,33 @@ 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:
+    LOG_INFO("DB", "List plt '%s' with null uri", ret->plt_name);
+    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);
+    LOG_INFO("DB", "List playlists with LIMIT %d and OFFSET %d",
+             ret->msg_count, (int) ret->continuation);
     return true;
 error:
     sqlite3_finalize(ret->stmt);
@@ -194,6 +237,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/database/open.c b/src/database/open.c
index f82bbc7bef7ab111aaf349ae4400f4b6ae04a151..8a2701acdf4f1ffaf657375f6e89821986dd4f21 100644
--- a/src/database/open.c
+++ b/src/database/open.c
@@ -12,6 +12,12 @@
 extern unsigned char ___src_database_disk_sql[];
 extern unsigned char ___src_database_memory_sql[];
 
+static const char *__pragmas =
+    "PRAGMA case_sensitive_like = false;\n"
+    "PRAGMA encoding            = 'UTF-8';\n"
+    "PRAGMA synchronous         = 0;\n"
+    "PRAGMA journal_mode        = off;\n";
+
 #define INVALID_CHARS_DBPATH    ":?!'\""
 #define HEAP_LIMIT_SOFT         100 * 1024 * 1024
 #define HEAP_LIMIT_HARD         150 * 1024 * 1024
@@ -91,6 +97,7 @@ is_sql_str_invalid(const char *str)
 bool
 database_new(volatile sqlite3 **db)
 {
+    *db = NULL;
     static int flags = SQLITE_OPEN_READWRITE    |
                        SQLITE_OPEN_SHAREDCACHE  |
                        SQLITE_OPEN_NOFOLLOW     |
@@ -101,10 +108,15 @@ database_new(volatile sqlite3 **db)
         SQLITE_OK != sqlite3_open_v2(":memory:", (sqlite3 **) db, flags, NULL))
         return false;
     SQLITE_EXEC(*db, (const char *) ___src_database_memory_sql, err_not_init);
+    SQLITE_EXEC(*db, __pragmas, err_pragma);
     return true;
 err_not_init:
     *db = NULL;
     return false;
+err_pragma:
+    sqlite3_close((sqlite3 *) *db);
+    *db = NULL;
+    return false;
 }
 
 static inline bool
diff --git a/src/main/lkt.c b/src/main/lkt.c
index ecf48701a9824fcfc2c2b9a4cfdaacb1b24217d9..ce715f6108672d1ff340f2539a6aa2595c8ff3c0 100644
--- a/src/main/lkt.c
+++ b/src/main/lkt.c
@@ -130,12 +130,12 @@ write_socket(FILE *sock, const char *format, ...)
     buff[LKT_MESSAGE_MAX - 1] = 0;
 
     if (size < 0)
-        fail("Connexion error")
+        fail("Connexion error");
 
-        if (fwrite(buff, sizeof(char), size, sock) < (unsigned int) size)
-            fail("Connexion error")
+    if (fwrite(buff, sizeof(char), size, sock) < (unsigned int) size)
+        fail("Connexion error");
 
-            va_end(ap);
+    va_end(ap);
 }
 
 noreturn static inline void
@@ -752,8 +752,11 @@ plt_delete__(struct lkt_cmd_args *args)
 {
     FILE *sock = lkt_connect();
     char buff[2];
-    fail_if(args->argc < 1, "Invalid argument");
-    send_cmd_with_uri(sock, "playlistdelete", args->argc, args->argv);
+    char cmd[128];
+    fail_if(args->argc < 3, "Invalid argument");
+    snprintf(cmd, 128 - 1, "playlistdelete %s", args->argv[0]);
+    cmd[127] = '\0';
+    send_cmd_with_uri(sock, cmd, args->argc - 1, &args->argv[1]);
     exit_with_status(sock, buff);
 }
 
@@ -816,6 +819,52 @@ stickers_delete__(struct lkt_cmd_args *args)
 
 /* Search functions. */
 
+noreturn void
+__continuation_calls(const char *cmd)
+{
+    char buff[LKT_MESSAGE_MAX];
+    int continuation = 0;
+    FILE *sock = NULL;
+redo:
+    sock = lkt_connect();
+
+    write_socket(sock, "%d %s\n", continuation, cmd);
+    for (;;) {
+        memset(buff, 0, LKT_MESSAGE_MAX * sizeof(char));
+        read_socket(sock, buff, LKT_MESSAGE_MAX - 1);
+
+        if (STR_NMATCH(buff, "continue:", strlen("continue:"))) {
+            continuation = atoi(lkt_skip_key(buff));
+            fclose(sock);
+            goto redo;
+        }
+
+        if (STR_NMATCH(buff, "OK", 2))
+            exit(EXIT_SUCCESS);
+        else if (STR_NMATCH(buff, "ACK", 3))
+            exit(EXIT_FAILURE);
+
+        fprintf(stdout, "%s", buff);
+    }
+}
+
+noreturn void
+list_plt__(struct lkt_cmd_args *args)
+{
+    fail_if(args->argc != 0, "Invalid argument number");
+    __continuation_calls("listplaylists");
+}
+
+noreturn void
+list_plt_content__(struct lkt_cmd_args *args)
+{
+    fail_if(args->argc != 1, "Invalid argument number");
+    char buff[LKT_MESSAGE_MAX];
+    safe_snprintf(buff, LKT_MESSAGE_MAX, "listplaylist %s", args->argv[0]);
+    __continuation_calls(buff);
+    exit(EXIT_FAILURE);
+}
+
 noreturn void
 search_with_cmd__(struct lkt_cmd_args *args, const char *cmd)
 {
@@ -875,6 +924,11 @@ search_get__(struct lkt_cmd_args *args)
 noreturn void
 search_plt__(struct lkt_cmd_args *args)
 {
+    if (args->argc == 0)
+        list_plt__(args);
+    if (args->argc == 1)
+        list_plt_content__(args);
+
     fail_if(args->argc < 3, "Invalid number of arguments");
     fail_if(!lkt_valid_type(args->argv[1]), "Invalid type for query");
     char buff[LKT_MESSAGE_MAX];
@@ -883,7 +937,7 @@ search_plt__(struct lkt_cmd_args *args)
 redo:
     sock = lkt_connect();
 
-    write_socket(sock, "%d listplaylist %s %s://", continuation,
+    write_socket(sock, "%d listplaylistinfo %s %s://", continuation,
                  args->argv[0], args->argv[1]);
     for (i = 2; i < args->argc - 1; ++i)
         write_socket(sock, "%s ", args->argv[i]);
@@ -936,6 +990,7 @@ static struct lkt_cmd_opt options_plt[] = {
     { .name = "delete",     .call = plt_delete__  },
     { .name = "destroy",    .call = plt_destroy__ },
     { .name = "create",     .call = plt_create__  },
+    { .name = "list",       .call = search_plt__  },
     LKT_OPT_NULL,
 };
 
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);