diff --git a/NEWS b/NEWS
index 6ae634e73..0caf58e5d 100644
--- a/NEWS
+++ b/NEWS
@@ -12,6 +12,8 @@ ver 0.22 (not yet released)
   - ffmpeg: allow partial reads
 * archive
   - iso9660: support seeking
+* playlist
+  - cue: integrate contents in database
 * decoder
   - mad: remove option "gapless", always do gapless
   - sidplay: add option "default_genre"
diff --git a/src/db/plugins/simple/Directory.hxx b/src/db/plugins/simple/Directory.hxx
index 32c20c061..1647dbfad 100644
--- a/src/db/plugins/simple/Directory.hxx
+++ b/src/db/plugins/simple/Directory.hxx
@@ -44,6 +44,12 @@ static constexpr unsigned DEVICE_INARCHIVE = -1;
  */
 static constexpr unsigned DEVICE_CONTAINER = -2;
 
+/**
+ * Virtual directory that is really a playlist file (special value for
+ * Directory::device).
+ */
+static constexpr unsigned DEVICE_PLAYLIST = -3;
+
 class SongFilter;
 
 struct Directory {
@@ -117,6 +123,7 @@ public:
 	 */
 	bool IsReallyAFile() const noexcept {
 		return device == DEVICE_INARCHIVE ||
+			device == DEVICE_PLAYLIST ||
 			device == DEVICE_CONTAINER;
 	}
 
diff --git a/src/db/plugins/simple/DirectorySave.cxx b/src/db/plugins/simple/DirectorySave.cxx
index 1dedba063..ade511e97 100644
--- a/src/db/plugins/simple/DirectorySave.cxx
+++ b/src/db/plugins/simple/DirectorySave.cxx
@@ -50,6 +50,9 @@ DeviceToTypeString(unsigned device) noexcept
 	case DEVICE_CONTAINER:
 		return "container";
 
+	case DEVICE_PLAYLIST:
+		return "playlist";
+
 	default:
 		return nullptr;
 	}
@@ -63,6 +66,8 @@ ParseTypeString(const char *type) noexcept
 		return DEVICE_INARCHIVE;
 	else if (StringIsEqual(type, "container"))
 		return DEVICE_CONTAINER;
+	else if (StringIsEqual(type, "playlist"))
+		return DEVICE_PLAYLIST;
 	else
 		return 0;
 }
diff --git a/src/db/update/Playlist.cxx b/src/db/update/Playlist.cxx
index 93275dc91..4d4a9bb1d 100644
--- a/src/db/update/Playlist.cxx
+++ b/src/db/update/Playlist.cxx
@@ -18,24 +18,91 @@
  */
 
 #include "Walk.hxx"
+#include "UpdateDomain.hxx"
 #include "db/DatabaseLock.hxx"
 #include "db/PlaylistVector.hxx"
 #include "db/plugins/simple/Directory.hxx"
+#include "song/DetachedSong.hxx"
+#include "input/InputStream.hxx"
+#include "playlist/PlaylistPlugin.hxx"
 #include "playlist/PlaylistRegistry.hxx"
+#include "playlist/PlaylistStream.hxx"
+#include "playlist/SongEnumerator.hxx"
 #include "storage/FileInfo.hxx"
+#include "storage/StorageInterface.hxx"
+#include "util/StringFormat.hxx"
+#include "Log.hxx"
+
+void
+UpdateWalk::UpdatePlaylistFile(Directory &parent, const char *name,
+			       const StorageFileInfo &info,
+			       const PlaylistPlugin &plugin) noexcept
+{
+	assert(plugin.open_stream);
+
+	Directory *directory =
+		LockMakeVirtualDirectoryIfModified(parent, name, info,
+						   DEVICE_PLAYLIST);
+	if (directory == nullptr)
+		/* not modified */
+		return;
+
+	const auto uri_utf8 = storage.MapUTF8(directory->GetPath());
+
+	FormatDebug(update_domain, "scanning playlist '%s'", uri_utf8.c_str());
+
+	try {
+		Mutex mutex;
+		auto e = plugin.open_stream(InputStream::OpenReady(uri_utf8.c_str(),
+								   mutex));
+		if (!e) {
+			/* unsupported URI? roll back.. */
+			editor.LockDeleteDirectory(directory);
+			return;
+		}
+
+		unsigned track = 0;
+
+		while (true) {
+			auto song = e->NextSong();
+			if (!song)
+				break;
+
+			auto db_song = std::make_unique<Song>(std::move(*song),
+							      *directory);
+			db_song->target = "../" + db_song->filename;
+			db_song->filename = StringFormat<64>("track%04u",
+							     ++track);
+
+			{
+				const ScopeDatabaseLock protect;
+				directory->AddSong(std::move(db_song));
+			}
+		}
+	} catch (...) {
+		FormatError(std::current_exception(),
+			    "Failed to scan playlist '%s'", uri_utf8.c_str());
+		editor.LockDeleteDirectory(directory);
+	}
+}
 
 bool
 UpdateWalk::UpdatePlaylistFile(Directory &directory,
 			       const char *name, const char *suffix,
 			       const StorageFileInfo &info) noexcept
 {
-	if (!playlist_suffix_supported(suffix))
+	const auto *const plugin = FindPlaylistPluginBySuffix(suffix);
+	if (plugin == nullptr)
 		return false;
 
+	if (plugin->as_folder)
+		UpdatePlaylistFile(directory, name, info, *plugin);
+
 	PlaylistInfo pi(name, info.mtime);
 
 	const ScopeDatabaseLock protect;
 	if (directory.playlists.UpdateOrInsert(std::move(pi)))
 		modified = true;
+
 	return true;
 }
diff --git a/src/db/update/Walk.hxx b/src/db/update/Walk.hxx
index e54e398b9..72b0e7822 100644
--- a/src/db/update/Walk.hxx
+++ b/src/db/update/Walk.hxx
@@ -30,6 +30,7 @@
 struct StorageFileInfo;
 struct Directory;
 struct ArchivePlugin;
+struct PlaylistPlugin;
 class ArchiveFile;
 class Storage;
 class ExcludeList;
@@ -118,6 +119,10 @@ private:
 	}
 #endif
 
+	void UpdatePlaylistFile(Directory &parent, const char *name,
+				const StorageFileInfo &info,
+				const PlaylistPlugin &plugin) noexcept;
+
 	bool UpdatePlaylistFile(Directory &directory,
 				const char *name, const char *suffix,
 				const StorageFileInfo &info) noexcept;
diff --git a/src/playlist/PlaylistPlugin.hxx b/src/playlist/PlaylistPlugin.hxx
index 4661ed967..449435ccb 100644
--- a/src/playlist/PlaylistPlugin.hxx
+++ b/src/playlist/PlaylistPlugin.hxx
@@ -69,6 +69,12 @@ struct PlaylistPlugin {
 	const char *const*suffixes = nullptr;
 	const char *const*mime_types = nullptr;
 
+	/**
+	 * If true, then playlists of this type are shown in the
+	 * database as folders.
+	 */
+	bool as_folder = false;
+
 	constexpr PlaylistPlugin(const char *_name,
 				 std::unique_ptr<SongEnumerator> (*_open_uri)(const char *uri,
 									      Mutex &mutex)) noexcept
@@ -104,6 +110,12 @@ struct PlaylistPlugin {
 		return copy;
 	}
 
+	constexpr auto WithAsFolder(bool value=true) noexcept {
+		auto copy = *this;
+		copy.as_folder = value;
+		return copy;
+	}
+
 	/**
 	 * Does the plugin announce the specified URI scheme?
 	 */
diff --git a/src/playlist/plugins/CuePlaylistPlugin.cxx b/src/playlist/plugins/CuePlaylistPlugin.cxx
index 137eb4e90..b8d645c94 100644
--- a/src/playlist/plugins/CuePlaylistPlugin.cxx
+++ b/src/playlist/plugins/CuePlaylistPlugin.cxx
@@ -72,5 +72,6 @@ static const char *const cue_playlist_mime_types[] = {
 
 const PlaylistPlugin cue_playlist_plugin =
 	PlaylistPlugin("cue", cue_playlist_open_stream)
+	.WithAsFolder()
 	.WithSuffixes(cue_playlist_suffixes)
 	.WithMimeTypes(cue_playlist_mime_types);