diff --git a/intl/msg_hash_lbl.h b/intl/msg_hash_lbl.h
index 21467bc466..900dcbc64e 100644
--- a/intl/msg_hash_lbl.h
+++ b/intl/msg_hash_lbl.h
@@ -2314,6 +2314,10 @@ MSG_HASH(
    MENU_ENUM_LABEL_PLAYLIST_MANAGER_CLEAN_PLAYLIST,
    "playlist_manager_clean_playlist"
    )
+MSG_HASH(
+   MENU_ENUM_LABEL_PLAYLIST_MANAGER_REFRESH_PLAYLIST,
+   "playlist_manager_refresh_playlist"
+   )
 MSG_HASH(
    MENU_ENUM_LABEL_PLAYLIST_SETTINGS_BEGIN,
    "playlist_settings_begin"
@@ -5006,6 +5010,10 @@ MSG_HASH(
    MENU_ENUM_LABEL_MANUAL_CONTENT_SCAN_OVERWRITE,
    "manual_content_scan_overwrite"
    )
+MSG_HASH(
+   MENU_ENUM_LABEL_MANUAL_CONTENT_SCAN_VALIDATE_ENTRIES,
+   "manual_content_scan_validate_entries"
+   )
 MSG_HASH(
    MENU_ENUM_LABEL_MANUAL_CONTENT_SCAN_START,
    "manual_content_scan_start"
diff --git a/intl/msg_hash_us.h b/intl/msg_hash_us.h
index ef34cf36e7..31710d76e5 100644
--- a/intl/msg_hash_us.h
+++ b/intl/msg_hash_us.h
@@ -5559,6 +5559,14 @@ MSG_HASH(
    MENU_ENUM_SUBLABEL_PLAYLIST_MANAGER_CLEAN_PLAYLIST,
    "Validate core associations and remove invalid and duplicate entries."
    )
+MSG_HASH(
+   MENU_ENUM_LABEL_VALUE_PLAYLIST_MANAGER_REFRESH_PLAYLIST,
+   "Refresh Playlist"
+   )
+MSG_HASH(
+   MENU_ENUM_SUBLABEL_PLAYLIST_MANAGER_REFRESH_PLAYLIST,
+   "Add new content and remove invalid entries by repeating the 'Manual Scan' operation last used to create or edit the playlist."
+   )
 MSG_HASH(
    MENU_ENUM_LABEL_VALUE_DELETE_PLAYLIST,
    "Delete Playlist"
@@ -6151,6 +6159,14 @@ MSG_HASH(
    MENU_ENUM_SUBLABEL_MANUAL_CONTENT_SCAN_OVERWRITE,
    "When enabled, any existing playlist will be deleted before scanning content. When disabled, existing playlist entries are preserved and only content currently missing from the playlist will be added."
    )
+MSG_HASH(
+   MENU_ENUM_LABEL_VALUE_MANUAL_CONTENT_SCAN_VALIDATE_ENTRIES,
+   "Validate Existing Entries"
+   )
+MSG_HASH(
+   MENU_ENUM_SUBLABEL_MANUAL_CONTENT_SCAN_VALIDATE_ENTRIES,
+   "When enabled, entries in any existing playlist will be verified before before scanning new content. Entries referring to missing content and/or files with invalid extensions will be removed."
+   )
 MSG_HASH(
    MENU_ENUM_LABEL_VALUE_MANUAL_CONTENT_SCAN_START,
    "Start Scan"
@@ -10893,6 +10909,30 @@ MSG_HASH(
    MSG_PLAYLIST_MANAGER_PLAYLIST_CLEANED,
    "Playlist cleaned: "
    )
+MSG_HASH(
+   MSG_PLAYLIST_MANAGER_REFRESH_MISSING_CONFIG,
+   "Refresh failed - playlist contains no valid scan record: "
+   )
+MSG_HASH(
+   MSG_PLAYLIST_MANAGER_REFRESH_INVALID_CONTENT_DIR,
+   "Refresh failed - invalid/missing content directory: "
+   )
+MSG_HASH(
+   MSG_PLAYLIST_MANAGER_REFRESH_INVALID_SYSTEM_NAME,
+   "Refresh failed - invalid/missing system name: "
+   )
+MSG_HASH(
+   MSG_PLAYLIST_MANAGER_REFRESH_INVALID_CORE,
+   "Refresh failed - invalid core: "
+   )
+MSG_HASH(
+   MSG_PLAYLIST_MANAGER_REFRESH_INVALID_DAT_FILE,
+   "Refresh failed - invalid/missing arcade DAT file: "
+   )
+MSG_HASH(
+   MSG_PLAYLIST_MANAGER_REFRESH_DAT_FILE_TOO_LARGE,
+   "Refresh failed - arcade DAT file too large (insufficient memory): "
+   )
 MSG_HASH(
    MSG_ADDED_TO_FAVORITES,
    "Added to favorites"
@@ -12085,6 +12125,10 @@ MSG_HASH(
    MSG_MANUAL_CONTENT_SCAN_START,
    "Scanning content: "
    )
+MSG_HASH(
+   MSG_MANUAL_CONTENT_SCAN_PLAYLIST_CLEANUP,
+   "Checking current entries: "
+   )
 MSG_HASH(
    MSG_MANUAL_CONTENT_SCAN_IN_PROGRESS,
    "Scanning: "
diff --git a/manual_content_scan.c b/manual_content_scan.c
index 84cfa2047b..466c92ffb5 100644
--- a/manual_content_scan.c
+++ b/manual_content_scan.c
@@ -56,6 +56,7 @@ typedef struct
    bool search_archives;
    bool filter_dat_content;
    bool overwrite_playlist;
+   bool validate_entries;
 } scan_settings_t;
 
 /* TODO/FIXME - static public global variables */
@@ -83,7 +84,8 @@ static scan_settings_t scan_settings = {
    true,                                        /* search_recursively */
    false,                                       /* search_archives */
    false,                                       /* filter_dat_content */
-   false                                        /* overwrite_playlist */
+   false,                                       /* overwrite_playlist */
+   false                                        /* validate_entries */
 };
 
 /*****************/
@@ -176,6 +178,13 @@ bool *manual_content_scan_get_overwrite_playlist_ptr(void)
    return &scan_settings.overwrite_playlist;
 }
 
+/* Returns a pointer to the internal
+ * 'validate_entries' bool */
+bool *manual_content_scan_get_validate_entries_ptr(void)
+{
+   return &scan_settings.validate_entries;
+}
+
 /* Sanitisation */
 
 /* Sanitises file extensions list string:
@@ -493,6 +502,234 @@ error:
    return false;
 }
 
+/* Sets all parameters for the next manual scan
+ * operation according the to recorded values in
+ * the specified playlist.
+ * Returns MANUAL_CONTENT_SCAN_PLAYLIST_REFRESH_OK
+ * if playlist contains a valid scan record. */
+enum manual_content_scan_playlist_refresh_status
+      manual_content_scan_set_menu_from_playlist(playlist_t *playlist,
+            const char *path_content_database, bool show_hidden_files)
+{
+   const char *playlist_path    = NULL;
+   const char *playlist_file    = NULL;
+   const char *content_dir      = NULL;
+   const char *core_name        = NULL;
+   const char *file_exts        = NULL;
+   const char *dat_file_path    = NULL;
+   bool search_recursively      = false;
+   bool search_archives         = false;
+   bool filter_dat_content      = false;
+#ifdef HAVE_LIBRETRODB
+   struct string_list *rdb_list = NULL;
+#endif
+   enum manual_content_scan_system_name_type
+         system_name_type       = MANUAL_CONTENT_SCAN_SYSTEM_NAME_CONTENT_DIR;
+   enum manual_content_scan_core_type
+         core_type              = MANUAL_CONTENT_SCAN_CORE_DETECT;
+   enum manual_content_scan_playlist_refresh_status
+         playlist_status        = MANUAL_CONTENT_SCAN_PLAYLIST_REFRESH_OK;
+   char system_name[PATH_MAX_LENGTH];
+
+   system_name[0] = '\0';
+
+   if (!playlist_scan_refresh_enabled(playlist))
+   {
+      playlist_status = MANUAL_CONTENT_SCAN_PLAYLIST_REFRESH_MISSING_CONFIG;
+      goto end;
+   }
+
+   /* Read scan parameters from playlist */
+   playlist_path      = playlist_get_conf_path(playlist);
+   content_dir        = playlist_get_scan_content_dir(playlist);
+   core_name          = playlist_get_default_core_name(playlist);
+   file_exts          = playlist_get_scan_file_exts(playlist);
+   dat_file_path      = playlist_get_scan_dat_file_path(playlist);
+
+   search_recursively = playlist_get_scan_search_recursively(playlist);
+   search_archives    = playlist_get_scan_search_archives(playlist);
+   filter_dat_content = playlist_get_scan_filter_dat_content(playlist);
+
+   /* Determine system name (playlist basename
+    * without extension) */
+   if (string_is_empty(playlist_path))
+   {
+      /* Cannot happen, but would constitute a
+       * 'system name' error */
+      playlist_status = MANUAL_CONTENT_SCAN_PLAYLIST_REFRESH_INVALID_SYSTEM_NAME;
+      goto end;
+   }
+
+   if ((playlist_file = path_basename(playlist_path)))
+   {
+      strlcpy(system_name, playlist_file, sizeof(system_name));
+      path_remove_extension(system_name);
+   }
+
+   if (string_is_empty(system_name))
+   {
+      /* Cannot happen, but would constitute a
+       * 'system name' error */
+      playlist_status = MANUAL_CONTENT_SCAN_PLAYLIST_REFRESH_INVALID_SYSTEM_NAME;
+      goto end;
+   }
+
+   /* Set content directory */
+   if (!manual_content_scan_set_menu_content_dir(content_dir))
+   {
+      playlist_status = MANUAL_CONTENT_SCAN_PLAYLIST_REFRESH_INVALID_CONTENT_DIR;
+      goto end;
+   }
+
+   /* Set system name */
+#ifdef HAVE_LIBRETRODB
+   /* > If platform has database support, get names
+    *   of all installed database files */
+   rdb_list = dir_list_new_special(
+         path_content_database,
+         DIR_LIST_DATABASES, NULL, show_hidden_files);
+
+   if (rdb_list && rdb_list->size)
+   {
+      size_t i;
+
+      /* Loop over database files */
+      for (i = 0; i < rdb_list->size; i++)
+      {
+         const char *rdb_path = rdb_list->elems[i].data;
+         const char *rdb_file = NULL;
+         char rdb_name[PATH_MAX_LENGTH];
+
+         rdb_name[0] = '\0';
+
+         /* Sanity check */
+         if (string_is_empty(rdb_path))
+            continue;
+
+         rdb_file = path_basename(rdb_path);
+
+         if (string_is_empty(rdb_file))
+            continue;
+
+         /* Remove file extension */
+         strlcpy(rdb_name, rdb_file, sizeof(rdb_name));
+         path_remove_extension(rdb_name);
+
+         if (string_is_empty(rdb_name))
+            continue;
+
+         /* Check whether playlist system name
+          * matches current database file */
+         if (string_is_equal(system_name, rdb_name))
+         {
+            system_name_type = MANUAL_CONTENT_SCAN_SYSTEM_NAME_DATABASE;
+            break;
+         }
+      }
+   }
+
+   string_list_free(rdb_list);
+#endif
+
+   /* > If system name does not match a database
+    *   file, then check whether it matches the
+    *   content directory name */
+   if (system_name_type !=
+         MANUAL_CONTENT_SCAN_SYSTEM_NAME_DATABASE)
+   {
+      /* system_name_type is set to
+       * MANUAL_CONTENT_SCAN_SYSTEM_NAME_CONTENT_DIR
+       * by default - so if a match is found just
+       * reset 'custom name' field */
+      if (string_is_equal(system_name,
+            scan_settings.system_name_content_dir))
+         scan_settings.system_name_custom[0] = '\0';
+      else
+      {
+         /* Playlist is using a custom system name */
+         system_name_type = MANUAL_CONTENT_SCAN_SYSTEM_NAME_CUSTOM;
+         strlcpy(scan_settings.system_name_custom, system_name,
+               sizeof(scan_settings.system_name_custom));
+      }
+   }
+
+   if (!manual_content_scan_set_menu_system_name(
+         system_name_type, system_name))
+   {
+      playlist_status = MANUAL_CONTENT_SCAN_PLAYLIST_REFRESH_INVALID_SYSTEM_NAME;
+      goto end;
+   }
+
+   /* Set core path/name */
+   if (!string_is_empty(core_name) &&
+       !string_is_equal(core_name, FILE_PATH_DETECT))
+      core_type = MANUAL_CONTENT_SCAN_CORE_SET;
+
+   if (!manual_content_scan_set_menu_core_name(
+         core_type, core_name))
+   {
+      playlist_status = MANUAL_CONTENT_SCAN_PLAYLIST_REFRESH_INVALID_CORE;
+      goto end;
+   }
+
+   /* Set custom file extensions */
+   if (string_is_empty(file_exts))
+      scan_settings.file_exts_custom[0] = '\0';
+   else
+   {
+      strlcpy(scan_settings.file_exts_custom, file_exts,
+            sizeof(scan_settings.file_exts_custom));
+
+      /* File extensions read from playlist should
+       * be correctly formatted, with '|' characters
+       * as delimiters
+       * > For menu purposes, must replace these
+       *   delimiters with space characters
+       * > Additionally scrub the resultant string,
+       *   to handle the case where a user has
+       *   'corrupted' it by manually tampering with
+       *   the playlist file */
+      string_replace_all_chars(scan_settings.file_exts_custom, '|', ' ');
+      manual_content_scan_scrub_file_exts(scan_settings.file_exts_custom);
+   }
+
+   /* Set DAT file path */
+   if (string_is_empty(dat_file_path))
+      scan_settings.dat_file_path[0] = '\0';
+   else
+   {
+      strlcpy(scan_settings.dat_file_path, dat_file_path,
+            sizeof(scan_settings.dat_file_path));
+
+      switch (manual_content_scan_validate_dat_file_path())
+      {
+         case MANUAL_CONTENT_SCAN_DAT_FILE_INVALID:
+            playlist_status = MANUAL_CONTENT_SCAN_PLAYLIST_REFRESH_INVALID_DAT_FILE;
+            goto end;
+         case MANUAL_CONTENT_SCAN_DAT_FILE_TOO_LARGE:
+            playlist_status = MANUAL_CONTENT_SCAN_PLAYLIST_REFRESH_DAT_FILE_TOO_LARGE;
+            goto end;
+         default:
+            /* No action required */
+            break;
+      }
+   }
+
+   /* Set remaining boolean parameters */
+   scan_settings.search_recursively = search_recursively;
+   scan_settings.search_archives    = search_archives;
+   scan_settings.filter_dat_content = filter_dat_content;
+   /* When refreshing a playlist:
+    * > We never overwrite the existing file
+    * > We always validate entries in the
+    *   existing file */
+   scan_settings.overwrite_playlist = false;
+   scan_settings.validate_entries   = true;
+
+end:
+   return playlist_status;
+}
+
 /* Menu getters */
 
 /* Fetches content directory for next manual scan
@@ -858,11 +1095,15 @@ bool manual_content_scan_get_task_config(
    }
 
    /* Get file extensions list */
+   task_config->file_exts_custom_set = false;
    if (!string_is_empty(scan_settings.file_exts_custom))
+   {
+      task_config->file_exts_custom_set = true;
       strlcpy(
             task_config->file_exts,
             scan_settings.file_exts_custom,
             sizeof(task_config->file_exts));
+   }
    else if (scan_settings.core_type == MANUAL_CONTENT_SCAN_CORE_SET)
       if (!string_is_empty(scan_settings.file_exts_core))
          strlcpy(
@@ -890,15 +1131,14 @@ bool manual_content_scan_get_task_config(
 
    /* Copy 'search recursively' setting */
    task_config->search_recursively = scan_settings.search_recursively;
-
    /* Copy 'search inside archives' setting */
-   task_config->search_archives = scan_settings.search_archives;
-
+   task_config->search_archives    = scan_settings.search_archives;
    /* Copy 'DAT file filter' setting */
    task_config->filter_dat_content = scan_settings.filter_dat_content;
-
    /* Copy 'overwrite playlist' setting */
    task_config->overwrite_playlist = scan_settings.overwrite_playlist;
+   /* Copy 'validate_entries' setting */
+   task_config->validate_entries   = scan_settings.validate_entries;
 
    return true;
 }
@@ -907,7 +1147,8 @@ bool manual_content_scan_get_task_config(
  * content directory
  * > Returns NULL in the event of failure
  * > Returned string list must be free()'d */
-struct string_list *manual_content_scan_get_content_list(manual_content_scan_task_config_t *task_config)
+struct string_list *manual_content_scan_get_content_list(
+      manual_content_scan_task_config_t *task_config)
 {
    struct string_list *dir_list = NULL;
    bool filter_exts;
@@ -1155,8 +1396,8 @@ void manual_content_scan_add_content_to_playlist(
        *   so these casts are safe */
       entry.path      = (char*)playlist_content_path;
       entry.label     = label;
-      entry.core_path = (char*)"DETECT";
-      entry.core_name = (char*)"DETECT";
+      entry.core_path = (char*)FILE_PATH_DETECT;
+      entry.core_name = (char*)FILE_PATH_DETECT;
       entry.crc32     = (char*)"00000000|crc";
       entry.db_name   = task_config->database_name;
 
diff --git a/manual_content_scan.h b/manual_content_scan.h
index 64f4eb3b07..23b0818543 100644
--- a/manual_content_scan.h
+++ b/manual_content_scan.h
@@ -65,6 +65,19 @@ enum manual_content_scan_dat_file_path_status
    MANUAL_CONTENT_SCAN_DAT_FILE_TOO_LARGE
 };
 
+/* Defines all possible return values for
+ * manual_content_scan_set_menu_from_playlist() */
+enum manual_content_scan_playlist_refresh_status
+{
+   MANUAL_CONTENT_SCAN_PLAYLIST_REFRESH_OK = 0,
+   MANUAL_CONTENT_SCAN_PLAYLIST_REFRESH_MISSING_CONFIG,
+   MANUAL_CONTENT_SCAN_PLAYLIST_REFRESH_INVALID_CONTENT_DIR,
+   MANUAL_CONTENT_SCAN_PLAYLIST_REFRESH_INVALID_SYSTEM_NAME,
+   MANUAL_CONTENT_SCAN_PLAYLIST_REFRESH_INVALID_CORE,
+   MANUAL_CONTENT_SCAN_PLAYLIST_REFRESH_INVALID_DAT_FILE,
+   MANUAL_CONTENT_SCAN_PLAYLIST_REFRESH_DAT_FILE_TOO_LARGE
+};
+
 /* Holds all configuration parameters required
  * for a manual content scan task */
 typedef struct
@@ -78,10 +91,12 @@ typedef struct
    char file_exts[PATH_MAX_LENGTH];
    char dat_file_path[PATH_MAX_LENGTH];
    bool core_set;
+   bool file_exts_custom_set;
    bool search_recursively;
    bool search_archives;
    bool filter_dat_content;
    bool overwrite_playlist;
+   bool validate_entries;
 } manual_content_scan_task_config_t;
 
 /*****************/
@@ -142,6 +157,10 @@ bool *manual_content_scan_get_filter_dat_content_ptr(void);
  * 'overwrite_playlist' bool */
 bool *manual_content_scan_get_overwrite_playlist_ptr(void);
 
+/* Returns a pointer to the internal
+ * 'validate_entries' bool */
+bool *manual_content_scan_get_validate_entries_ptr(void);
+
 /* Sanitisation */
 
 /* Removes invalid characters from
@@ -187,6 +206,15 @@ bool manual_content_scan_set_menu_core_name(
       enum manual_content_scan_core_type core_type,
       const char *core_name);
 
+/* Sets all parameters for the next manual scan
+ * operation according the to recorded values in
+ * the specified playlist.
+ * Returns MANUAL_CONTENT_SCAN_PLAYLIST_REFRESH_OK
+ * if playlist contains a valid scan record. */
+enum manual_content_scan_playlist_refresh_status
+      manual_content_scan_set_menu_from_playlist(playlist_t *playlist,
+            const char *path_content_database, bool show_hidden_files);
+
 /* Menu getters */
 
 /* Fetches content directory for next manual scan
@@ -239,7 +267,8 @@ bool manual_content_scan_get_task_config(
  * content directory
  * > Returns NULL in the event of failure
  * > Returned string list must be free()'d */
-struct string_list *manual_content_scan_get_content_list(manual_content_scan_task_config_t *task_config);
+struct string_list *manual_content_scan_get_content_list(
+      manual_content_scan_task_config_t *task_config);
 
 /* Adds specified content to playlist, if not already
  * present */
diff --git a/menu/cbs/menu_cbs_ok.c b/menu/cbs/menu_cbs_ok.c
index 486b1f9d7f..0788050618 100644
--- a/menu/cbs/menu_cbs_ok.c
+++ b/menu/cbs/menu_cbs_ok.c
@@ -7023,7 +7023,9 @@ static int action_ok_manual_content_scan_start(const char *path,
    playlist_config.old_format          = settings->bools.playlist_use_old_format;
    playlist_config.compress            = settings->bools.playlist_compression;
    playlist_config.fuzzy_archive_match = settings->bools.playlist_fuzzy_archive_match;
-   playlist_config_set_base_content_directory(&playlist_config, settings->bools.playlist_portable_paths ? settings->paths.directory_menu_content : NULL);
+   playlist_config_set_base_content_directory(&playlist_config,
+         settings->bools.playlist_portable_paths ?
+               settings->paths.directory_menu_content : NULL);
 
    task_push_manual_content_scan(&playlist_config, directory_playlist);
    return 0;
@@ -7460,6 +7462,120 @@ static int action_ok_playlist_clean(const char *path,
    return 0;
 }
 
+static int action_ok_playlist_refresh(const char *path,
+      const char *label, unsigned type, size_t idx, size_t entry_idx)
+{
+   playlist_config_t *playlist_config = NULL;
+   playlist_t *playlist               = playlist_get_cached();
+   settings_t *settings               = config_get_ptr();
+   bool scan_record_valid             = false;
+   const char *msg_prefix             = NULL;
+   const char *msg_subject            = NULL;
+   const char *log_text               = NULL;
+   char system_name[256];
+
+   system_name[0] = '\0';
+
+   if (!playlist || !settings)
+      return -1;
+
+   playlist_config = playlist_get_config(playlist);
+
+   if (!playlist_config || string_is_empty(playlist_config->path))
+      return -1;
+
+   /* Configure manual scan using playlist record */
+   switch (manual_content_scan_set_menu_from_playlist(playlist,
+         settings->paths.path_content_database,
+         settings->bools.show_hidden_files))
+   {
+      case MANUAL_CONTENT_SCAN_PLAYLIST_REFRESH_OK:
+         scan_record_valid = true;
+         break;
+      case MANUAL_CONTENT_SCAN_PLAYLIST_REFRESH_INVALID_CONTENT_DIR:
+         msg_prefix  = msg_hash_to_str(MSG_PLAYLIST_MANAGER_REFRESH_INVALID_CONTENT_DIR);
+         msg_subject = playlist_get_scan_content_dir(playlist);
+         log_text    = "[Playlist Refresh]: Invalid content directory: %s\n";
+         break;
+      case MANUAL_CONTENT_SCAN_PLAYLIST_REFRESH_INVALID_SYSTEM_NAME:
+         {
+            const char *playlist_file = NULL;
+
+            if ((playlist_file = path_basename(playlist_config->path)))
+            {
+               strlcpy(system_name, playlist_file, sizeof(system_name));
+               path_remove_extension(system_name);
+            }
+
+            msg_prefix  = msg_hash_to_str(MSG_PLAYLIST_MANAGER_REFRESH_INVALID_SYSTEM_NAME);
+            msg_subject = system_name;
+            log_text    = "[Playlist Refresh]: Invalid system name: %s\n";
+         }
+         break;
+      case MANUAL_CONTENT_SCAN_PLAYLIST_REFRESH_INVALID_CORE:
+         msg_prefix  = msg_hash_to_str(MSG_PLAYLIST_MANAGER_REFRESH_INVALID_CORE);
+         msg_subject = playlist_get_default_core_name(playlist);
+         log_text    = "[Playlist Refresh]: Invalid core name: %s\n";
+         break;
+      case MANUAL_CONTENT_SCAN_PLAYLIST_REFRESH_INVALID_DAT_FILE:
+         msg_prefix  = msg_hash_to_str(MSG_PLAYLIST_MANAGER_REFRESH_INVALID_DAT_FILE);
+         msg_subject = playlist_get_scan_dat_file_path(playlist);
+         log_text    = "[Playlist Refresh]: Invalid arcade dat file: %s\n";
+         break;
+      case MANUAL_CONTENT_SCAN_PLAYLIST_REFRESH_DAT_FILE_TOO_LARGE:
+         msg_prefix  = msg_hash_to_str(MSG_PLAYLIST_MANAGER_REFRESH_DAT_FILE_TOO_LARGE);
+         msg_subject = playlist_get_scan_dat_file_path(playlist);
+         log_text    = "[Playlist Refresh]: Arcade dat file too large: %s\n";
+         break;
+      case MANUAL_CONTENT_SCAN_PLAYLIST_REFRESH_MISSING_CONFIG:
+      default:
+         msg_prefix  = msg_hash_to_str(MSG_PLAYLIST_MANAGER_REFRESH_MISSING_CONFIG);
+         msg_subject = path_basename(playlist_config->path);
+         log_text    = "[Playlist Refresh]: No scan record found: %s\n";
+         break;
+   }
+
+   /* Log errors in the event of an invalid
+    * scan record */
+   if (!scan_record_valid)
+   {
+      char msg[PATH_MAX_LENGTH];
+      msg[0] = '\0';
+
+      if (string_is_empty(msg_subject))
+         msg_subject = msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NOT_AVAILABLE);
+
+      snprintf(msg, sizeof(msg), "%s%s", msg_prefix, msg_subject);
+
+      RARCH_ERR(log_text, msg_subject);
+      runloop_msg_queue_push(msg, 1, 150, true,
+            NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
+
+      /* Even though this is a failure condition, we
+       * return 0 here to suppress any refreshing of
+       * the menu (this can appear ugly, depending
+       * on the active menu driver...) */
+      return 0;
+   }
+
+   /* Perform manual scan
+    * > Since we are refreshing the playlist,
+    *   additionally ensure that all pertinent
+    *   'playlist_config' parameters are synchronised
+    *   with the current settings struct */
+   playlist_config->capacity            = COLLECTION_SIZE;
+   playlist_config->old_format          = settings->bools.playlist_use_old_format;
+   playlist_config->compress            = settings->bools.playlist_compression;
+   playlist_config->fuzzy_archive_match = settings->bools.playlist_fuzzy_archive_match;
+   playlist_config_set_base_content_directory(playlist_config,
+         settings->bools.playlist_portable_paths ?
+               settings->paths.directory_menu_content : NULL);
+
+   task_push_manual_content_scan(playlist_config,
+         settings->paths.directory_playlist);
+   return 0;
+}
+
 static int is_rdb_entry(enum msg_hash_enums enum_idx)
 {
    switch (enum_idx)
@@ -7740,6 +7856,7 @@ static int menu_cbs_init_bind_ok_compare_label(menu_file_list_cbs_t *cbs,
          {MENU_ENUM_LABEL_PLAYLIST_MANAGER_SETTINGS,           action_ok_push_playlist_manager_settings},
          {MENU_ENUM_LABEL_PLAYLIST_MANAGER_RESET_CORES,        action_ok_playlist_reset_cores},
          {MENU_ENUM_LABEL_PLAYLIST_MANAGER_CLEAN_PLAYLIST,     action_ok_playlist_clean},
+         {MENU_ENUM_LABEL_PLAYLIST_MANAGER_REFRESH_PLAYLIST,   action_ok_playlist_refresh},
          {MENU_ENUM_LABEL_RECORDING_SETTINGS,                  action_ok_push_recording_settings_list},
          {MENU_ENUM_LABEL_INPUT_HOTKEY_BINDS,                  action_ok_push_input_hotkey_binds_list},
          {MENU_ENUM_LABEL_ACCOUNTS_RETRO_ACHIEVEMENTS,         action_ok_push_accounts_cheevos_list},
diff --git a/menu/cbs/menu_cbs_sublabel.c b/menu/cbs/menu_cbs_sublabel.c
index 8aad58186a..fc230d1872 100644
--- a/menu/cbs/menu_cbs_sublabel.c
+++ b/menu/cbs/menu_cbs_sublabel.c
@@ -168,6 +168,7 @@ DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_playlist_manager_reset_cores,  MENU_
 DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_playlist_manager_label_display_mode, MENU_ENUM_SUBLABEL_PLAYLIST_MANAGER_LABEL_DISPLAY_MODE)
 DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_playlist_manager_sort_mode, MENU_ENUM_SUBLABEL_PLAYLIST_MANAGER_SORT_MODE)
 DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_playlist_manager_clean_playlist, MENU_ENUM_SUBLABEL_PLAYLIST_MANAGER_CLEAN_PLAYLIST)
+DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_playlist_manager_refresh_playlist, MENU_ENUM_SUBLABEL_PLAYLIST_MANAGER_REFRESH_PLAYLIST)
 DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_delete_playlist,               MENU_ENUM_SUBLABEL_DELETE_PLAYLIST)
 DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_network_settings_list,         MENU_ENUM_SUBLABEL_NETWORK_SETTINGS)
 DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_network_on_demand_thumbnails,  MENU_ENUM_SUBLABEL_NETWORK_ON_DEMAND_THUMBNAILS)
@@ -985,6 +986,7 @@ DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_manual_content_scan_search_archives,
 DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_manual_content_scan_dat_file,                  MENU_ENUM_SUBLABEL_MANUAL_CONTENT_SCAN_DAT_FILE)
 DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_manual_content_scan_dat_file_filter,           MENU_ENUM_SUBLABEL_MANUAL_CONTENT_SCAN_DAT_FILE_FILTER)
 DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_manual_content_scan_overwrite,                 MENU_ENUM_SUBLABEL_MANUAL_CONTENT_SCAN_OVERWRITE)
+DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_manual_content_scan_validate_entries,          MENU_ENUM_SUBLABEL_MANUAL_CONTENT_SCAN_VALIDATE_ENTRIES)
 DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_manual_content_scan_start,                     MENU_ENUM_SUBLABEL_MANUAL_CONTENT_SCAN_START)
 DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_core_create_backup,                            MENU_ENUM_SUBLABEL_CORE_CREATE_BACKUP)
 DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_core_restore_backup_list,                      MENU_ENUM_SUBLABEL_CORE_RESTORE_BACKUP_LIST)
@@ -3960,6 +3962,9 @@ int menu_cbs_init_bind_sublabel(menu_file_list_cbs_t *cbs,
          case MENU_ENUM_LABEL_PLAYLIST_MANAGER_CLEAN_PLAYLIST:
             BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_playlist_manager_clean_playlist);
             break;
+         case MENU_ENUM_LABEL_PLAYLIST_MANAGER_REFRESH_PLAYLIST:
+            BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_playlist_manager_refresh_playlist);
+            break;
          case MENU_ENUM_LABEL_PLAYLIST_MANAGER_RIGHT_THUMBNAIL_MODE:
             {
                const char *menu_ident             = menu_driver_ident();
@@ -4400,6 +4405,9 @@ int menu_cbs_init_bind_sublabel(menu_file_list_cbs_t *cbs,
          case MENU_ENUM_LABEL_MANUAL_CONTENT_SCAN_OVERWRITE:
             BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_manual_content_scan_overwrite);
             break;
+         case MENU_ENUM_LABEL_MANUAL_CONTENT_SCAN_VALIDATE_ENTRIES:
+            BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_manual_content_scan_validate_entries);
+            break;
          case MENU_ENUM_LABEL_MANUAL_CONTENT_SCAN_START:
             BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_manual_content_scan_start);
             break;
diff --git a/menu/drivers/materialui.c b/menu/drivers/materialui.c
index 184cc44bd6..50248d8bcd 100644
--- a/menu/drivers/materialui.c
+++ b/menu/drivers/materialui.c
@@ -10453,7 +10453,8 @@ static void materialui_list_insert(
                   string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_UPDATE_OVERLAYS)) ||
                   string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_UPDATE_CG_SHADERS)) ||
                   string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_UPDATE_GLSL_SHADERS)) ||
-                  string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_UPDATE_SLANG_SHADERS))
+                  string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_UPDATE_SLANG_SHADERS)) ||
+                  string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_PLAYLIST_MANAGER_REFRESH_PLAYLIST))
                   )
                   {
                      node->icon_texture_index = MUI_TEXTURE_UPDATER;
diff --git a/menu/drivers/ozone.c b/menu/drivers/ozone.c
index 4a5533847b..4a89cd6206 100644
--- a/menu/drivers/ozone.c
+++ b/menu/drivers/ozone.c
@@ -1743,6 +1743,7 @@ static uintptr_t ozone_entries_icon_get_texture(ozone_handle_t *ozone,
       case MENU_ENUM_LABEL_AUTOSAVE_INTERVAL:
       case MENU_ENUM_LABEL_FRAME_TIME_COUNTER_SETTINGS:
       case MENU_ENUM_LABEL_PLAYLIST_MANAGER_CLEAN_PLAYLIST:
+      case MENU_ENUM_LABEL_PLAYLIST_MANAGER_REFRESH_PLAYLIST:
             return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_RELOAD];
       case MENU_ENUM_LABEL_SHUTDOWN:
             return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_SHUTDOWN];
diff --git a/menu/drivers/xmb.c b/menu/drivers/xmb.c
index 9b4de964cb..7f821ad51b 100644
--- a/menu/drivers/xmb.c
+++ b/menu/drivers/xmb.c
@@ -2601,6 +2601,7 @@ static uintptr_t xmb_icon_get_id(xmb_handle_t *xmb,
       case MENU_ENUM_LABEL_AUTOSAVE_INTERVAL:
       case MENU_ENUM_LABEL_FRAME_TIME_COUNTER_SETTINGS:
       case MENU_ENUM_LABEL_PLAYLIST_MANAGER_CLEAN_PLAYLIST:
+      case MENU_ENUM_LABEL_PLAYLIST_MANAGER_REFRESH_PLAYLIST:
          return xmb->textures.list[XMB_TEXTURE_RELOAD];
       case MENU_ENUM_LABEL_RENAME_ENTRY:
          return xmb->textures.list[XMB_TEXTURE_RENAME];
diff --git a/menu/menu_displaylist.c b/menu/menu_displaylist.c
index 7c5883c43c..7c9fc696f0 100644
--- a/menu/menu_displaylist.c
+++ b/menu/menu_displaylist.c
@@ -4035,6 +4035,14 @@ static bool menu_displaylist_parse_playlist_manager_settings(
             MENU_ENUM_LABEL_PLAYLIST_MANAGER_SORT_MODE,
             MENU_SETTING_PLAYLIST_MANAGER_SORT_MODE, 0, 0);
 
+   /* Refresh playlist */
+   if (playlist_scan_refresh_enabled(playlist))
+      menu_entries_append_enum(info->list,
+            msg_hash_to_str(MENU_ENUM_LABEL_VALUE_PLAYLIST_MANAGER_REFRESH_PLAYLIST),
+            msg_hash_to_str(MENU_ENUM_LABEL_PLAYLIST_MANAGER_REFRESH_PLAYLIST),
+            MENU_ENUM_LABEL_PLAYLIST_MANAGER_REFRESH_PLAYLIST,
+            MENU_SETTING_ACTION_PLAYLIST_MANAGER_REFRESH_PLAYLIST, 0, 0);
+
    /* Clean playlist */
    menu_entries_append_enum(info->list,
          msg_hash_to_str(MENU_ENUM_LABEL_VALUE_PLAYLIST_MANAGER_CLEAN_PLAYLIST),
@@ -5240,6 +5248,13 @@ static bool menu_displaylist_parse_manual_content_scan_list(
          false) == 0)
       count++;
 
+   /* Validate existing entries */
+   if (!(*manual_content_scan_get_overwrite_playlist_ptr()) &&
+       MENU_DISPLAYLIST_PARSE_SETTINGS_ENUM(info->list,
+         MENU_ENUM_LABEL_MANUAL_CONTENT_SCAN_VALIDATE_ENTRIES, PARSE_ONLY_BOOL,
+         false) == 0)
+      count++;
+
    /* Start scan */
    if (menu_entries_append_enum(info->list,
          msg_hash_to_str(MENU_ENUM_LABEL_VALUE_MANUAL_CONTENT_SCAN_START),
diff --git a/menu/menu_driver.h b/menu/menu_driver.h
index 8b78d02e42..e768780214 100644
--- a/menu/menu_driver.h
+++ b/menu/menu_driver.h
@@ -243,6 +243,7 @@ enum menu_settings_type
    MENU_SETTING_ACTION_DELETE_PLAYLIST,
    MENU_SETTING_ACTION_PLAYLIST_MANAGER_RESET_CORES,
    MENU_SETTING_ACTION_PLAYLIST_MANAGER_CLEAN_PLAYLIST,
+   MENU_SETTING_ACTION_PLAYLIST_MANAGER_REFRESH_PLAYLIST,
 
    MENU_SETTING_MANUAL_CONTENT_SCAN_DIR,
    MENU_SETTING_MANUAL_CONTENT_SCAN_SYSTEM_NAME,
diff --git a/menu/menu_setting.c b/menu/menu_setting.c
index dfd68e12f1..a2d41038dd 100644
--- a/menu/menu_setting.c
+++ b/menu/menu_setting.c
@@ -20428,6 +20428,24 @@ static bool setting_append_list(
                general_write_handler,
                general_read_handler,
                SD_FLAG_NONE);
+         (*list)[list_info->index - 1].action_ok    = setting_bool_action_left_with_refresh;
+         (*list)[list_info->index - 1].action_left  = setting_bool_action_left_with_refresh;
+         (*list)[list_info->index - 1].action_right = setting_bool_action_right_with_refresh;
+
+         CONFIG_BOOL(
+               list, list_info,
+               manual_content_scan_get_validate_entries_ptr(),
+               MENU_ENUM_LABEL_MANUAL_CONTENT_SCAN_VALIDATE_ENTRIES,
+               MENU_ENUM_LABEL_VALUE_MANUAL_CONTENT_SCAN_VALIDATE_ENTRIES,
+               false,
+               MENU_ENUM_LABEL_VALUE_OFF,
+               MENU_ENUM_LABEL_VALUE_ON,
+               &group_info,
+               &subgroup_info,
+               parent_group,
+               general_write_handler,
+               general_read_handler,
+               SD_FLAG_NONE);
 
          END_SUB_GROUP(list, list_info, parent_group);
          END_GROUP(list, list_info, parent_group);
diff --git a/msg_hash.h b/msg_hash.h
index 3f59f58015..506dee6399 100644
--- a/msg_hash.h
+++ b/msg_hash.h
@@ -2411,6 +2411,15 @@ enum msg_hash_enums
    MSG_PLAYLIST_MANAGER_CLEANING_PLAYLIST,
    MSG_PLAYLIST_MANAGER_PLAYLIST_CLEANED,
 
+   MENU_LABEL(PLAYLIST_MANAGER_REFRESH_PLAYLIST),
+
+   MSG_PLAYLIST_MANAGER_REFRESH_MISSING_CONFIG,
+   MSG_PLAYLIST_MANAGER_REFRESH_INVALID_CONTENT_DIR,
+   MSG_PLAYLIST_MANAGER_REFRESH_INVALID_SYSTEM_NAME,
+   MSG_PLAYLIST_MANAGER_REFRESH_INVALID_CORE,
+   MSG_PLAYLIST_MANAGER_REFRESH_INVALID_DAT_FILE,
+   MSG_PLAYLIST_MANAGER_REFRESH_DAT_FILE_TOO_LARGE,
+
    MENU_LABEL(CORE_UPDATER_SETTINGS),
    MENU_LABEL(LAKKA_SERVICES),
    MENU_LABEL(SHADER_APPLY_CHANGES),
@@ -3203,6 +3212,7 @@ enum msg_hash_enums
    MENU_LABEL(MANUAL_CONTENT_SCAN_DAT_FILE),
    MENU_LABEL(MANUAL_CONTENT_SCAN_DAT_FILE_FILTER),
    MENU_LABEL(MANUAL_CONTENT_SCAN_OVERWRITE),
+   MENU_LABEL(MANUAL_CONTENT_SCAN_VALIDATE_ENTRIES),
    MENU_LABEL(MANUAL_CONTENT_SCAN_START),
 
    MENU_ENUM_LABEL_VALUE_MANUAL_CONTENT_SCAN_SYSTEM_NAME_USE_CONTENT_DIR,
@@ -3216,6 +3226,7 @@ enum msg_hash_enums
    MSG_MANUAL_CONTENT_SCAN_INVALID_CONFIG,
    MSG_MANUAL_CONTENT_SCAN_INVALID_CONTENT,
    MSG_MANUAL_CONTENT_SCAN_START,
+   MSG_MANUAL_CONTENT_SCAN_PLAYLIST_CLEANUP,
    MSG_MANUAL_CONTENT_SCAN_IN_PROGRESS,
    MSG_MANUAL_CONTENT_SCAN_M3U_CLEANUP,
    MSG_MANUAL_CONTENT_SCAN_END,
diff --git a/playlist.c b/playlist.c
index 4860b4c46b..8391dcf115 100644
--- a/playlist.c
+++ b/playlist.c
@@ -28,6 +28,7 @@
 #include <string/stdstring.h>
 #include <streams/interface_stream.h>
 #include <file/file_path.h>
+#include <file/archive_file.h>
 #include <lists/string_list.h>
 #include <formats/rjson.h>
 #include <array/rbuf.h>
@@ -56,6 +57,19 @@
 #define USING_POSIX_FILE_SYSTEM
 #endif
 
+/* Holds all configuration parameters required
+ * to repeat a manual content scan for a
+ * previously manual-scan-generated playlist */
+typedef struct
+{
+   char *content_dir;
+   char *file_exts;
+   char *dat_file_path;
+   bool search_recursively;
+   bool search_archives;
+   bool filter_dat_content;
+} playlist_manual_scan_record_t;
+
 struct content_playlist
 {
    char *default_core_path;
@@ -64,7 +78,8 @@ struct content_playlist
 
    struct playlist_entry *entries;
 
-   playlist_config_t config;  /* size_t alignment */
+   playlist_manual_scan_record_t scan_record; /* ptr alignment */
+   playlist_config_t config;                  /* size_t alignment */
 
    enum playlist_label_display_mode label_display_mode;
    enum playlist_thumbnail_mode right_thumbnail_mode;
@@ -85,6 +100,7 @@ typedef struct
    enum playlist_label_display_mode *current_meta_label_display_mode_val;
    enum playlist_thumbnail_mode *current_meta_thumbnail_mode_val;
    enum playlist_sort_mode *current_meta_sort_mode_val;
+   bool *current_meta_bool_val;
    playlist_t *playlist;
 
    unsigned array_depth;
@@ -1123,6 +1139,89 @@ void playlist_resolve_path(enum playlist_file_mode mode,
 #endif
 }
 
+/**
+ * playlist_content_path_is_valid:
+ * @path      : Content path
+ *
+ * Checks whether specified playlist content path
+ * refers to an existent file. Handles all playlist
+ * content path 'types' (i.e. can validate paths
+ * referencing files inside archives).
+ *
+ * Returns true if file referenced by content
+ * path exists on the host filesystem.
+ **/
+bool playlist_content_path_is_valid(const char *path)
+{
+   /* Sanity check */
+   if (string_is_empty(path))
+      return false;
+
+   /* If content is inside an archive, special
+    * handling is required... */
+   if (path_contains_compressed_file(path))
+   {
+      const char *delim                  = path_get_archive_delim(path);
+      char archive_path[PATH_MAX_LENGTH] = {0};
+      size_t len                         = 0;
+      struct string_list *archive_list   = NULL;
+      const char *content_file           = NULL;
+      bool content_found                 = false;
+
+      if (!delim)
+         return false;
+
+      /* Get path of 'parent' archive file */
+      len = (size_t)(1 + delim - path);
+      strlcpy(archive_path, path,
+            (len < PATH_MAX_LENGTH ? len : PATH_MAX_LENGTH) * sizeof(char));
+
+      /* Check if archive itself exists */
+      if (!path_is_valid(archive_path))
+         return false;
+
+      /* Check if file exists inside archive */
+      archive_list = file_archive_get_file_list(archive_path, NULL);
+
+      if (!archive_list)
+         return false;
+
+      /* > Get playlist entry content file name
+       *   (sans archive file path) */
+      content_file = delim;
+      content_file++;
+
+      if (!string_is_empty(content_file))
+      {
+         size_t i;
+
+         /* > Loop over archive file contents */
+         for (i = 0; i < archive_list->size; i++)
+         {
+            const char *archive_file = archive_list->elems[i].data;
+
+            if (string_is_empty(archive_file))
+               continue;
+
+            if (string_is_equal(content_file, archive_file))
+            {
+               content_found = true;
+               break;
+            }
+         }
+      }
+
+      /* Clean up */
+      string_list_free(archive_list);
+
+      return content_found;
+   }
+   /* This is a 'normal' path - just check if
+    * it's valid */
+   else
+      return path_is_valid(path);
+}
+
 /**
  * playlist_push:
  * @playlist        	   : Playlist handle.
@@ -1640,7 +1739,7 @@ void playlist_write_file(playlist_t *playlist)
       rjsonwriter_add_string(writer, "version");
       rjsonwriter_add_colon(writer);
       rjsonwriter_add_space(writer);
-      rjsonwriter_add_string(writer, "1.4");
+      rjsonwriter_add_string(writer, "1.5");
       rjsonwriter_add_comma(writer);
       rjsonwriter_add_newline(writer);
 
@@ -1703,6 +1802,57 @@ void playlist_write_file(playlist_t *playlist)
       rjsonwriter_add_comma(writer);
       rjsonwriter_add_newline(writer);
 
+      if (!string_is_empty(playlist->scan_record.content_dir))
+      {
+         rjsonwriter_add_spaces(writer, 2);
+         rjsonwriter_add_string(writer, "scan_content_dir");
+         rjsonwriter_add_colon(writer);
+         rjsonwriter_add_space(writer);
+         rjsonwriter_add_string(writer, playlist->scan_record.content_dir);
+         rjsonwriter_add_comma(writer);
+         rjsonwriter_add_newline(writer);
+
+         rjsonwriter_add_spaces(writer, 2);
+         rjsonwriter_add_string(writer, "scan_file_exts");
+         rjsonwriter_add_colon(writer);
+         rjsonwriter_add_space(writer);
+         rjsonwriter_add_string(writer, playlist->scan_record.file_exts);
+         rjsonwriter_add_comma(writer);
+         rjsonwriter_add_newline(writer);
+
+         rjsonwriter_add_spaces(writer, 2);
+         rjsonwriter_add_string(writer, "scan_dat_file_path");
+         rjsonwriter_add_colon(writer);
+         rjsonwriter_add_space(writer);
+         rjsonwriter_add_string(writer, playlist->scan_record.dat_file_path);
+         rjsonwriter_add_comma(writer);
+         rjsonwriter_add_newline(writer);
+
+         rjsonwriter_add_spaces(writer, 2);
+         rjsonwriter_add_string(writer, "scan_search_recursively");
+         rjsonwriter_add_colon(writer);
+         rjsonwriter_add_space(writer);
+         rjsonwriter_add_bool(writer, playlist->scan_record.search_recursively);
+         rjsonwriter_add_comma(writer);
+         rjsonwriter_add_newline(writer);
+
+         rjsonwriter_add_spaces(writer, 2);
+         rjsonwriter_add_string(writer, "scan_search_archives");
+         rjsonwriter_add_colon(writer);
+         rjsonwriter_add_space(writer);
+         rjsonwriter_add_bool(writer, playlist->scan_record.search_archives);
+         rjsonwriter_add_comma(writer);
+         rjsonwriter_add_newline(writer);
+
+         rjsonwriter_add_spaces(writer, 2);
+         rjsonwriter_add_string(writer, "scan_filter_dat_content");
+         rjsonwriter_add_colon(writer);
+         rjsonwriter_add_space(writer);
+         rjsonwriter_add_bool(writer, playlist->scan_record.filter_dat_content);
+         rjsonwriter_add_comma(writer);
+         rjsonwriter_add_newline(writer);
+      }
+
       rjsonwriter_add_spaces(writer, 2);
       rjsonwriter_add_string(writer, "items");
       rjsonwriter_add_colon(writer);
@@ -1878,6 +2028,18 @@ void playlist_free(playlist_t *playlist)
       free(playlist->base_content_directory);
    playlist->base_content_directory = NULL;
 
+   if (playlist->scan_record.content_dir)
+      free(playlist->scan_record.content_dir);
+   playlist->scan_record.content_dir = NULL;
+
+   if (playlist->scan_record.file_exts)
+      free(playlist->scan_record.file_exts);
+   playlist->scan_record.file_exts = NULL;
+
+   if (playlist->scan_record.dat_file_path)
+      free(playlist->scan_record.dat_file_path);
+   playlist->scan_record.dat_file_path = NULL;
+
    if (playlist->entries)
    {
       for (i = 0, len = RBUF_LEN(playlist->entries); i < len; i++)
@@ -2118,6 +2280,21 @@ static bool JSONNumberHandler(void *context, const char *pValue, size_t length)
    return true;
 }
 
+static bool JSONBoolHandler(void *context, bool value)
+{
+   JSONContext *pCtx = (JSONContext *)context;
+
+   if (!pCtx->in_items &&
+       (pCtx->object_depth == 1) &&
+       (pCtx->array_depth == 0) &&
+       pCtx->current_meta_bool_val)
+      *pCtx->current_meta_bool_val = value;
+
+   pCtx->current_meta_bool_val = NULL;
+
+   return true;
+}
+
 static bool JSONObjectMemberHandler(void *context, const char *pValue, size_t length)
 {
    JSONContext *pCtx = (JSONContext *)context;
@@ -2197,7 +2374,9 @@ static bool JSONObjectMemberHandler(void *context, const char *pValue, size_t le
       pCtx->current_meta_label_display_mode_val = NULL;
       pCtx->current_meta_thumbnail_mode_val     = NULL;
       pCtx->current_meta_sort_mode_val          = NULL;
+      pCtx->current_meta_bool_val               = NULL;
       pCtx->in_items                            = false;
+
       switch (pValue[0])
       {
          case 'b':
@@ -2205,7 +2384,7 @@ static bool JSONObjectMemberHandler(void *context, const char *pValue, size_t le
                pCtx->current_string_val = &pCtx->playlist->base_content_directory;
             break;
          case 'd':
-            if (string_is_equal(pValue, "default_core_path"))
+            if (string_is_equal(pValue,      "default_core_path"))
                pCtx->current_string_val = &pCtx->playlist->default_core_path;
             else if (string_is_equal(pValue, "default_core_name"))
                pCtx->current_string_val = &pCtx->playlist->default_core_name;
@@ -2215,17 +2394,29 @@ static bool JSONObjectMemberHandler(void *context, const char *pValue, size_t le
                pCtx->in_items = true;
             break;
          case 'l':
-            if (string_is_equal(pValue, "label_display_mode"))
+            if (string_is_equal(pValue,      "label_display_mode"))
                pCtx->current_meta_label_display_mode_val = &pCtx->playlist->label_display_mode;
             else if (string_is_equal(pValue, "left_thumbnail_mode"))
-               pCtx->current_meta_thumbnail_mode_val = &pCtx->playlist->left_thumbnail_mode;
+               pCtx->current_meta_thumbnail_mode_val     = &pCtx->playlist->left_thumbnail_mode;
             break;
          case 'r':
             if (string_is_equal(pValue, "right_thumbnail_mode"))
                pCtx->current_meta_thumbnail_mode_val = &pCtx->playlist->right_thumbnail_mode;
             break;
          case 's':
-            if (string_is_equal(pValue, "sort_mode"))
+            if (string_is_equal(pValue,      "scan_content_dir"))
+               pCtx->current_string_val         = &pCtx->playlist->scan_record.content_dir;
+            else if (string_is_equal(pValue, "scan_file_exts"))
+               pCtx->current_string_val         = &pCtx->playlist->scan_record.file_exts;
+            else if (string_is_equal(pValue, "scan_dat_file_path"))
+               pCtx->current_string_val         = &pCtx->playlist->scan_record.dat_file_path;
+            else if (string_is_equal(pValue, "scan_search_recursively"))
+               pCtx->current_meta_bool_val      = &pCtx->playlist->scan_record.search_recursively;
+            else if (string_is_equal(pValue, "scan_search_archives"))
+               pCtx->current_meta_bool_val      = &pCtx->playlist->scan_record.search_archives;
+            else if (string_is_equal(pValue, "scan_filter_dat_content"))
+               pCtx->current_meta_bool_val      = &pCtx->playlist->scan_record.filter_dat_content;
+            else if (string_is_equal(pValue, "sort_mode"))
                pCtx->current_meta_sort_mode_val = &pCtx->playlist->sort_mode;
             break;
       }
@@ -2338,7 +2529,8 @@ static bool playlist_read_file(playlist_t *playlist)
             JSONEndObjectHandler,
             JSONStartArrayHandler,
             JSONEndArrayHandler,
-            NULL, NULL) /* unused boolean/null handlers */
+            JSONBoolHandler,
+            NULL) /* Unused null handler */
             != RJSON_DONE)
       {
          if (context.out_of_memory)
@@ -2624,6 +2816,13 @@ playlist_t *playlist_init(const playlist_config_t *config)
    playlist->left_thumbnail_mode    = PLAYLIST_THUMBNAIL_MODE_DEFAULT;
    playlist->sort_mode              = PLAYLIST_SORT_MODE_DEFAULT;
 
+   playlist->scan_record.search_recursively = false;
+   playlist->scan_record.search_archives    = false;
+   playlist->scan_record.filter_dat_content = false;
+   playlist->scan_record.content_dir        = NULL;
+   playlist->scan_record.file_exts          = NULL;
+   playlist->scan_record.dat_file_path      = NULL;
+
    /* Cache configuration parameters */
    if (!playlist_config_copy(config, &playlist->config))
       goto error;
@@ -2634,7 +2833,9 @@ playlist_t *playlist_init(const playlist_config_t *config)
 
    /* Try auto-fixing paths if enabled, and playlist
     * base content directory is different */
-   if (config->autofix_paths && !string_is_equal(playlist->base_content_directory, config->base_content_directory))
+   if (config->autofix_paths &&
+       !string_is_equal(playlist->base_content_directory,
+            config->base_content_directory))
    {
       if (!string_is_empty(playlist->base_content_directory))
       {
@@ -2651,9 +2852,9 @@ playlist_t *playlist_init(const playlist_config_t *config)
             /* Fix entry path */
             tmp_entry_path[0] = '\0';
             path_replace_base_path_and_convert_to_local_file_system(
-               tmp_entry_path, entry->path,
-               playlist->base_content_directory, playlist->config.base_content_directory,
-               sizeof(tmp_entry_path));
+                  tmp_entry_path, entry->path,
+                  playlist->base_content_directory, playlist->config.base_content_directory,
+                  sizeof(tmp_entry_path));
 
             free(entry->path);
             entry->path = strdup(tmp_entry_path);
@@ -2676,9 +2877,9 @@ playlist_t *playlist_init(const playlist_config_t *config)
 
                   tmp_entry_path[0] = '\0';
                   path_replace_base_path_and_convert_to_local_file_system(
-                     tmp_entry_path, subsystem_rom_path,
-                     playlist->base_content_directory, playlist->config.base_content_directory,
-                     sizeof(tmp_entry_path));
+                        tmp_entry_path, subsystem_rom_path,
+                        playlist->base_content_directory, playlist->config.base_content_directory,
+                        sizeof(tmp_entry_path));
                   string_list_append(subsystem_roms_new_paths, tmp_entry_path, attributes);
                }
 
@@ -2686,6 +2887,32 @@ playlist_t *playlist_init(const playlist_config_t *config)
                entry->subsystem_roms = subsystem_roms_new_paths;
             }
          }
+
+         /* Fix scan record content directory */
+         if (!string_is_empty(playlist->scan_record.content_dir))
+         {
+            tmp_entry_path[0] = '\0';
+            path_replace_base_path_and_convert_to_local_file_system(
+                  tmp_entry_path, playlist->scan_record.content_dir,
+                  playlist->base_content_directory, playlist->config.base_content_directory,
+                  sizeof(tmp_entry_path));
+
+            free(playlist->scan_record.content_dir);
+            playlist->scan_record.content_dir = strdup(tmp_entry_path);
+         }
+
+         /* Fix scan record arcade DAT file */
+         if (!string_is_empty(playlist->scan_record.dat_file_path))
+         {
+            tmp_entry_path[0] = '\0';
+            path_replace_base_path_and_convert_to_local_file_system(
+                  tmp_entry_path, playlist->scan_record.dat_file_path,
+                  playlist->base_content_directory, playlist->config.base_content_directory,
+                  sizeof(tmp_entry_path));
+
+            free(playlist->scan_record.dat_file_path);
+            playlist->scan_record.dat_file_path = strdup(tmp_entry_path);
+         }
       }
 
       /* Update playlist base content directory*/
@@ -2958,14 +3185,14 @@ void playlist_get_db_name(playlist_t *playlist, size_t idx,
    }
 }
 
-char *playlist_get_default_core_path(playlist_t *playlist)
+const char *playlist_get_default_core_path(playlist_t *playlist)
 {
    if (!playlist)
       return NULL;
    return playlist->default_core_path;
 }
 
-char *playlist_get_default_core_name(playlist_t *playlist)
+const char *playlist_get_default_core_name(playlist_t *playlist)
 {
    if (!playlist)
       return NULL;
@@ -3001,6 +3228,55 @@ enum playlist_sort_mode playlist_get_sort_mode(playlist_t *playlist)
    return playlist->sort_mode;
 }
 
+const char *playlist_get_scan_content_dir(playlist_t *playlist)
+{
+   if (!playlist)
+      return NULL;
+   return playlist->scan_record.content_dir;
+}
+
+const char *playlist_get_scan_file_exts(playlist_t *playlist)
+{
+   if (!playlist)
+      return NULL;
+   return playlist->scan_record.file_exts;
+}
+
+const char *playlist_get_scan_dat_file_path(playlist_t *playlist)
+{
+   if (!playlist)
+      return NULL;
+   return playlist->scan_record.dat_file_path;
+}
+
+bool playlist_get_scan_search_recursively(playlist_t *playlist)
+{
+   if (!playlist)
+      return false;
+   return playlist->scan_record.search_recursively;
+}
+
+bool playlist_get_scan_search_archives(playlist_t *playlist)
+{
+   if (!playlist)
+      return false;
+   return playlist->scan_record.search_archives;
+}
+
+bool playlist_get_scan_filter_dat_content(playlist_t *playlist)
+{
+   if (!playlist)
+      return false;
+   return playlist->scan_record.filter_dat_content;
+}
+
+bool playlist_scan_refresh_enabled(playlist_t *playlist)
+{
+   if (!playlist)
+      return false;
+   return !string_is_empty(playlist->scan_record.content_dir);
+}
+
 void playlist_set_default_core_path(playlist_t *playlist, const char *core_path)
 {
    char real_core_path[PATH_MAX_LENGTH];
@@ -3090,6 +3366,135 @@ void playlist_set_sort_mode(playlist_t *playlist,
    }
 }
 
+void playlist_set_scan_content_dir(playlist_t *playlist, const char *content_dir)
+{
+   bool current_string_empty;
+   bool new_string_empty;
+
+   if (!playlist)
+      return;
+
+   current_string_empty = string_is_empty(playlist->scan_record.content_dir);
+   new_string_empty     = string_is_empty(content_dir);
+
+   /* Check whether string value has changed
+    * (note that a NULL or empty argument will
+    * unset the playlist value) */
+   if (( current_string_empty && !new_string_empty) ||
+       (!current_string_empty &&  new_string_empty) ||
+       !string_is_equal(playlist->scan_record.content_dir, content_dir))
+      playlist->modified = true;
+   else
+      return; /* Strings are identical; do nothing */
+
+   if (playlist->scan_record.content_dir)
+   {
+      free(playlist->scan_record.content_dir);
+      playlist->scan_record.content_dir = NULL;
+   }
+
+   if (!new_string_empty)
+      playlist->scan_record.content_dir = strdup(content_dir);
+}
+
+void playlist_set_scan_file_exts(playlist_t *playlist, const char *file_exts)
+{
+   bool current_string_empty;
+   bool new_string_empty;
+
+   if (!playlist)
+      return;
+
+   current_string_empty = string_is_empty(playlist->scan_record.file_exts);
+   new_string_empty     = string_is_empty(file_exts);
+
+   /* Check whether string value has changed
+    * (note that a NULL or empty argument will
+    * unset the playlist value) */
+   if (( current_string_empty && !new_string_empty) ||
+       (!current_string_empty &&  new_string_empty) ||
+       !string_is_equal(playlist->scan_record.file_exts, file_exts))
+      playlist->modified = true;
+   else
+      return; /* Strings are identical; do nothing */
+
+   if (playlist->scan_record.file_exts)
+   {
+      free(playlist->scan_record.file_exts);
+      playlist->scan_record.file_exts = NULL;
+   }
+
+   if (!new_string_empty)
+      playlist->scan_record.file_exts = strdup(file_exts);
+}
+
+void playlist_set_scan_dat_file_path(playlist_t *playlist, const char *dat_file_path)
+{
+   bool current_string_empty;
+   bool new_string_empty;
+
+   if (!playlist)
+      return;
+
+   current_string_empty = string_is_empty(playlist->scan_record.dat_file_path);
+   new_string_empty     = string_is_empty(dat_file_path);
+
+   /* Check whether string value has changed
+    * (note that a NULL or empty argument will
+    * unset the playlist value) */
+   if (( current_string_empty && !new_string_empty) ||
+       (!current_string_empty &&  new_string_empty) ||
+       !string_is_equal(playlist->scan_record.dat_file_path, dat_file_path))
+      playlist->modified = true;
+   else
+      return; /* Strings are identical; do nothing */
+
+   if (playlist->scan_record.dat_file_path)
+   {
+      free(playlist->scan_record.dat_file_path);
+      playlist->scan_record.dat_file_path = NULL;
+   }
+
+   if (!new_string_empty)
+      playlist->scan_record.dat_file_path = strdup(dat_file_path);
+}
+
+void playlist_set_scan_search_recursively(playlist_t *playlist, bool search_recursively)
+{
+   if (!playlist)
+      return;
+
+   if (playlist->scan_record.search_recursively != search_recursively)
+   {
+      playlist->scan_record.search_recursively = search_recursively;
+      playlist->modified = true;
+   }
+}
+
+void playlist_set_scan_search_archives(playlist_t *playlist, bool search_archives)
+{
+   if (!playlist)
+      return;
+
+   if (playlist->scan_record.search_archives != search_archives)
+   {
+      playlist->scan_record.search_archives = search_archives;
+      playlist->modified = true;
+   }
+}
+
+void playlist_set_scan_filter_dat_content(playlist_t *playlist, bool filter_dat_content)
+{
+   if (!playlist)
+      return;
+
+   if (playlist->scan_record.filter_dat_content != filter_dat_content)
+   {
+      playlist->scan_record.filter_dat_content = filter_dat_content;
+      playlist->modified = true;
+   }
+}
+
 /* Returns true if specified entry has a valid
  * core association (i.e. a non-empty string
  * other than DETECT) */
diff --git a/playlist.h b/playlist.h
index 09799d5eb3..cfd74fbc81 100644
--- a/playlist.h
+++ b/playlist.h
@@ -249,6 +249,20 @@ void playlist_delete_by_path(playlist_t *playlist,
 void playlist_resolve_path(enum playlist_file_mode mode,
       bool is_core, char *path, size_t len);
 
+/**
+ * playlist_content_path_is_valid:
+ * @path      : Content path
+ *
+ * Checks whether specified playlist content path
+ * refers to an existent file. Handles all playlist
+ * content path 'types' (i.e. can validate paths
+ * referencing files inside archives).
+ *
+ * Returns true if file referenced by content
+ * path exists on the host filesystem.
+ **/
+bool playlist_content_path_is_valid(const char *path);
+
 /**
  * playlist_push:
  * @playlist        	   : Playlist handle.
@@ -338,12 +352,19 @@ void playlist_get_crc32(playlist_t *playlist, size_t idx,
 void playlist_get_db_name(playlist_t *playlist, size_t idx,
       const char **db_name);
 
-char *playlist_get_default_core_path(playlist_t *playlist);
-char *playlist_get_default_core_name(playlist_t *playlist);
+const char *playlist_get_default_core_path(playlist_t *playlist);
+const char *playlist_get_default_core_name(playlist_t *playlist);
 enum playlist_label_display_mode playlist_get_label_display_mode(playlist_t *playlist);
 enum playlist_thumbnail_mode playlist_get_thumbnail_mode(
       playlist_t *playlist, enum playlist_thumbnail_id thumbnail_id);
 enum playlist_sort_mode playlist_get_sort_mode(playlist_t *playlist);
+const char *playlist_get_scan_content_dir(playlist_t *playlist);
+const char *playlist_get_scan_file_exts(playlist_t *playlist);
+const char *playlist_get_scan_dat_file_path(playlist_t *playlist);
+bool playlist_get_scan_search_recursively(playlist_t *playlist);
+bool playlist_get_scan_search_archives(playlist_t *playlist);
+bool playlist_get_scan_filter_dat_content(playlist_t *playlist);
+bool playlist_scan_refresh_enabled(playlist_t *playlist);
 
 void playlist_set_default_core_path(playlist_t *playlist, const char *core_path);
 void playlist_set_default_core_name(playlist_t *playlist, const char *core_name);
@@ -351,6 +372,12 @@ void playlist_set_label_display_mode(playlist_t *playlist, enum playlist_label_d
 void playlist_set_thumbnail_mode(
       playlist_t *playlist, enum playlist_thumbnail_id thumbnail_id, enum playlist_thumbnail_mode thumbnail_mode);
 void playlist_set_sort_mode(playlist_t *playlist, enum playlist_sort_mode sort_mode);
+void playlist_set_scan_content_dir(playlist_t *playlist, const char *content_dir);
+void playlist_set_scan_file_exts(playlist_t *playlist, const char *file_exts);
+void playlist_set_scan_dat_file_path(playlist_t *playlist, const char *dat_file_path);
+void playlist_set_scan_search_recursively(playlist_t *playlist, bool search_recursively);
+void playlist_set_scan_search_archives(playlist_t *playlist, bool search_archives);
+void playlist_set_scan_filter_dat_content(playlist_t *playlist, bool filter_dat_content);
 
 /* Returns true if specified entry has a valid
  * core association (i.e. a non-empty string
diff --git a/tasks/task_manual_content_scan.c b/tasks/task_manual_content_scan.c
index 82748ec959..84826ae75e 100644
--- a/tasks/task_manual_content_scan.c
+++ b/tasks/task_manual_content_scan.c
@@ -45,6 +45,7 @@
 enum manual_scan_status
 {
    MANUAL_SCAN_BEGIN = 0,
+   MANUAL_SCAN_ITERATE_CLEAN,
    MANUAL_SCAN_ITERATE_CONTENT,
    MANUAL_SCAN_ITERATE_M3U,
    MANUAL_SCAN_END
@@ -54,12 +55,15 @@ typedef struct manual_scan_handle
 {
    manual_content_scan_task_config_t *task_config;
    playlist_t *playlist;
+   struct string_list *file_exts_list;
    struct string_list *content_list;
    logiqx_dat_t *dat_file;
    struct string_list *m3u_list;
    playlist_config_t playlist_config; /* size_t alignment */
-   size_t list_size;
-   size_t list_index;
+   size_t playlist_size;
+   size_t playlist_index;
+   size_t content_list_size;
+   size_t content_list_index;
    size_t m3u_index;
    enum manual_scan_status status;
 } manual_scan_handle_t;
@@ -82,6 +86,12 @@ static void free_manual_content_scan_handle(manual_scan_handle_t *manual_scan)
       manual_scan->playlist = NULL;
    }
 
+   if (manual_scan->file_exts_list)
+   {
+      string_list_free(manual_scan->file_exts_list);
+      manual_scan->file_exts_list = NULL;
+   }
+
    if (manual_scan->content_list)
    {
       string_list_free(manual_scan->content_list);
@@ -199,6 +209,11 @@ static void task_manual_content_scan_handler(retro_task_t *task)
    {
       case MANUAL_SCAN_BEGIN:
          {
+            /* Get allowed file extensions list */
+            if (!string_is_empty(manual_scan->task_config->file_exts))
+               manual_scan->file_exts_list = string_split(
+                     manual_scan->task_config->file_exts, "|");
+
             /* Get content list */
             manual_scan->content_list = manual_content_scan_get_content_list(
                   manual_scan->task_config);
@@ -212,7 +227,7 @@ static void task_manual_content_scan_handler(retro_task_t *task)
                goto task_finished;
             }
 
-            manual_scan->list_size = manual_scan->content_list->size;
+            manual_scan->content_list_size = manual_scan->content_list->size;
 
             /* Load DAT file, if required */
             if (!string_is_empty(manual_scan->task_config->dat_file_path))
@@ -240,25 +255,118 @@ static void task_manual_content_scan_handler(retro_task_t *task)
             if (manual_scan->task_config->overwrite_playlist)
                playlist_clear(manual_scan->playlist);
 
+            /* Get initial playlist size */
+            manual_scan->playlist_size = playlist_size(manual_scan->playlist);
+
             /* Set default core, if required */
             if (manual_scan->task_config->core_set)
             {
-               playlist_set_default_core_path(
-                     manual_scan->playlist, manual_scan->task_config->core_path);
-               playlist_set_default_core_name(
-                     manual_scan->playlist, manual_scan->task_config->core_name);
+               playlist_set_default_core_path(manual_scan->playlist,
+                     manual_scan->task_config->core_path);
+               playlist_set_default_core_name(manual_scan->playlist,
+                     manual_scan->task_config->core_name);
             }
 
-            /* All good - can start iterating */
-            manual_scan->status = MANUAL_SCAN_ITERATE_CONTENT;
+            /* Record remaining scan parameters to enable
+             * subsequent 'refresh playlist' operations */
+            playlist_set_scan_content_dir(manual_scan->playlist,
+                  manual_scan->task_config->content_dir);
+            playlist_set_scan_file_exts(manual_scan->playlist,
+                  manual_scan->task_config->file_exts_custom_set ?
+                        manual_scan->task_config->file_exts : NULL);
+            playlist_set_scan_dat_file_path(manual_scan->playlist,
+                  manual_scan->task_config->dat_file_path);
+            playlist_set_scan_search_recursively(manual_scan->playlist,
+                  manual_scan->task_config->search_recursively);
+            playlist_set_scan_search_archives(manual_scan->playlist,
+                  manual_scan->task_config->search_archives);
+            playlist_set_scan_filter_dat_content(manual_scan->playlist,
+                  manual_scan->task_config->filter_dat_content);
+
+            /* All good - can start iterating
+             * > If playlist has content and 'validate
+             *   entries' is enabled, go to clean-up phase
+             * > Otherwise go straight to content scan phase */
+            if (manual_scan->task_config->validate_entries &&
+                (manual_scan->playlist_size > 0))
+               manual_scan->status = MANUAL_SCAN_ITERATE_CLEAN;
+            else
+               manual_scan->status = MANUAL_SCAN_ITERATE_CONTENT;
+         }
+         break;
+      case MANUAL_SCAN_ITERATE_CLEAN:
+         {
+            const struct playlist_entry *entry = NULL;
+            bool delete_entry                  = false;
+
+            /* Get current entry */
+            playlist_get_index(manual_scan->playlist,
+                  manual_scan->playlist_index, &entry);
+
+            if (entry)
+            {
+               const char *entry_file     = NULL;
+               const char *entry_file_ext = NULL;
+               char task_title[PATH_MAX_LENGTH];
+
+               task_title[0] = '\0';
+
+               /* Update progress display */
+               task_free_title(task);
+
+               strlcpy(task_title,
+                     msg_hash_to_str(MSG_MANUAL_CONTENT_SCAN_PLAYLIST_CLEANUP),
+                     sizeof(task_title));
+
+               if (!string_is_empty(entry->path) &&
+                   (entry_file = path_basename(entry->path)))
+                  strlcat(task_title, entry_file, sizeof(task_title));
+
+               task_set_title(task, strdup(task_title));
+               task_set_progress(task, (manual_scan->playlist_index * 100) /
+                     manual_scan->playlist_size);
+
+               /* Check whether playlist content exists on
+                * the filesystem */
+               if (!playlist_content_path_is_valid(entry->path))
+                  delete_entry = true;
+               /* If file exists, check whether it has a
+                * permitted file extension */
+               else if (manual_scan->file_exts_list &&
+                        (entry_file_ext = path_get_extension(entry->path)) &&
+                        !string_list_find_elem_prefix(
+                              manual_scan->file_exts_list,
+                              ".", entry_file_ext))
+                  delete_entry = true;
+
+               if (delete_entry)
+               {
+                  /* Invalid content - delete entry */
+                  playlist_delete_index(manual_scan->playlist,
+                        manual_scan->playlist_index);
+
+                  /* Update playlist_size */
+                  manual_scan->playlist_size = playlist_size(manual_scan->playlist);
+               }
+            }
+
+            /* Increment entry index *if* current entry still
+             * exists (i.e. if entry was deleted, current index
+             * will already point to the *next* entry) */
+            if (!delete_entry)
+               manual_scan->playlist_index++;
+
+            if (manual_scan->playlist_index >=
+                  manual_scan->playlist_size)
+               manual_scan->status = MANUAL_SCAN_ITERATE_CONTENT;
          }
          break;
       case MANUAL_SCAN_ITERATE_CONTENT:
          {
-            const char *content_path =
-                  manual_scan->content_list->elems[manual_scan->list_index].data;
-            int content_type         =
-                  manual_scan->content_list->elems[manual_scan->list_index].attr.i;
+            const char *content_path = manual_scan->content_list->elems[
+                  manual_scan->content_list_index].data;
+            int content_type         = manual_scan->content_list->elems[
+                  manual_scan->content_list_index].attr.i;
 
             if (!string_is_empty(content_path))
             {
@@ -270,15 +378,16 @@ static void task_manual_content_scan_handler(retro_task_t *task)
                /* Update progress display */
                task_free_title(task);
 
-               strlcpy(
-                     task_title, msg_hash_to_str(MSG_MANUAL_CONTENT_SCAN_IN_PROGRESS),
+               strlcpy(task_title,
+                     msg_hash_to_str(MSG_MANUAL_CONTENT_SCAN_IN_PROGRESS),
                      sizeof(task_title));
 
                if (!string_is_empty(content_file))
                   strlcat(task_title, content_file, sizeof(task_title));
 
                task_set_title(task, strdup(task_title));
-               task_set_progress(task, (manual_scan->list_index * 100) / manual_scan->list_size);
+               task_set_progress(task, (manual_scan->content_list_index * 100) /
+                     manual_scan->content_list_size);
 
                /* Add content to playlist */
                manual_content_scan_add_content_to_playlist(
@@ -300,8 +409,9 @@ static void task_manual_content_scan_handler(retro_task_t *task)
             }
 
             /* Increment content index */
-            manual_scan->list_index++;
-            if (manual_scan->list_index >= manual_scan->list_size)
+            manual_scan->content_list_index++;
+            if (manual_scan->content_list_index >=
+                  manual_scan->content_list_size)
             {
                /* Check whether we have any M3U files
                 * to process */
@@ -314,8 +424,8 @@ static void task_manual_content_scan_handler(retro_task_t *task)
          break;
       case MANUAL_SCAN_ITERATE_M3U:
          {
-            const char *m3u_path =
-                  manual_scan->m3u_list->elems[manual_scan->m3u_index].data;
+            const char *m3u_path = manual_scan->m3u_list->elems[
+                  manual_scan->m3u_index].data;
 
             if (!string_is_empty(m3u_path))
             {
@@ -328,15 +438,16 @@ static void task_manual_content_scan_handler(retro_task_t *task)
                /* Update progress display */
                task_free_title(task);
 
-               strlcpy(
-                     task_title, msg_hash_to_str(MSG_MANUAL_CONTENT_SCAN_M3U_CLEANUP),
+               strlcpy(task_title,
+                     msg_hash_to_str(MSG_MANUAL_CONTENT_SCAN_M3U_CLEANUP),
                      sizeof(task_title));
 
                if (!string_is_empty(m3u_name))
                   strlcat(task_title, m3u_name, sizeof(task_title));
 
                task_set_title(task, strdup(task_title));
-               task_set_progress(task, (manual_scan->m3u_index * 100) / manual_scan->m3u_list->size);
+               task_set_progress(task, (manual_scan->m3u_index * 100) /
+                     manual_scan->m3u_list->size);
 
                /* Load M3U file */
                m3u_file = m3u_file_init(m3u_path);
@@ -445,10 +556,13 @@ bool task_push_manual_content_scan(
    /* Configure handle */
    manual_scan->task_config         = NULL;
    manual_scan->playlist            = NULL;
+   manual_scan->file_exts_list      = NULL;
    manual_scan->content_list        = NULL;
    manual_scan->dat_file            = NULL;
-   manual_scan->list_size           = 0;
-   manual_scan->list_index          = 0;
+   manual_scan->playlist_size       = 0;
+   manual_scan->playlist_index      = 0;
+   manual_scan->content_list_size   = 0;
+   manual_scan->content_list_index  = 0;
    manual_scan->m3u_list            = string_list_new();
    manual_scan->m3u_index           = 0;
    manual_scan->status              = MANUAL_SCAN_BEGIN;
diff --git a/tasks/task_playlist_manager.c b/tasks/task_playlist_manager.c
index d92b1ec163..e65b115cf0 100644
--- a/tasks/task_playlist_manager.c
+++ b/tasks/task_playlist_manager.c
@@ -23,7 +23,6 @@
 #include <string/stdstring.h>
 #include <lists/string_list.h>
 #include <file/file_path.h>
-#include <file/archive_file.h>
 #include <formats/m3u_file.h>
 
 #include "tasks_internal.h"
@@ -368,78 +367,6 @@ error:
 /* Clean Playlist */
 /******************/
 
-static bool pl_manager_content_exists(const char *path)
-{
-   /* Sanity check */
-   if (string_is_empty(path))
-      return false;
-   
-   /* If content is inside an archive, special
-    * handling is required... */
-   if (path_contains_compressed_file(path))
-   {
-      const char *delim                  = path_get_archive_delim(path);
-      char archive_path[PATH_MAX_LENGTH] = {0};
-      size_t len                         = 0;
-      struct string_list *archive_list   = NULL;
-      const char *content_file           = NULL;
-      bool content_found                 = false;
-      
-      if (!delim)
-         return false;
-      
-      /* Get path of 'parent' archive file */
-      len = (size_t)(1 + delim - path);
-      strlcpy(
-            archive_path, path,
-            (len < PATH_MAX_LENGTH ? len : PATH_MAX_LENGTH) * sizeof(char));
-      
-      /* Check if archive itself exists */
-      if (!path_is_valid(archive_path))
-         return false;
-      
-      /* Check if file exists inside archive */
-      archive_list = file_archive_get_file_list(archive_path, NULL);
-      
-      if (!archive_list)
-         return false;
-      
-      /* > Get playlist entry content file name
-       *   (sans archive file path) */
-      content_file = delim;
-      content_file++;
-      
-      if (!string_is_empty(content_file))
-      {
-         size_t i;
-         
-         /* > Loop over archive file contents */
-         for (i = 0; i < archive_list->size; i++)
-         {
-            const char *archive_file = archive_list->elems[i].data;
-            
-            if (string_is_empty(archive_file))
-               continue;
-            
-            if (string_is_equal(content_file, archive_file))
-            {
-               content_found = true;
-               break;
-            }
-         }
-      }
-      
-      /* Clean up */
-      string_list_free(archive_list);
-      
-      return content_found;
-   }
-   /* This is a 'normal' path - just check if
-    * it's valid */
-   else
-      return path_is_valid(path);
-}
-
 static void pl_manager_validate_core_association(
       playlist_t *playlist, size_t entry_index,
       const char *core_path, const char *core_name)
@@ -562,7 +489,7 @@ static void task_pl_manager_clean_playlist_handler(retro_task_t *task)
             {
                /* Check whether playlist content exists on
                 * the filesystem */
-               if (!pl_manager_content_exists(entry->path))
+               if (!playlist_content_path_is_valid(entry->path))
                {
                   /* Invalid content - delete entry */
                   playlist_delete_index(pl_manager->playlist, pl_manager->list_index);