diff --git a/src/SongSave.cxx b/src/SongSave.cxx
index ce761e544..11c2ebcaf 100644
--- a/src/SongSave.cxx
+++ b/src/SongSave.cxx
@@ -53,6 +53,9 @@ song_save(BufferedOutputStream &os, const Song &song)
 {
 	os.Format(SONG_BEGIN "%s\n", song.filename.c_str());
 
+	if (!song.target.empty())
+		os.Format("Target: %s\n", song.target.c_str());
+
 	range_save(os, song.start_time.ToMS(), song.end_time.ToMS());
 
 	tag_save(os, song.tag);
@@ -83,6 +86,7 @@ song_save(BufferedOutputStream &os, const DetachedSong &song)
 
 DetachedSong
 song_load(TextFile &file, const char *uri,
+	  std::string *target_r,
 	  AudioFormat *audio_format_r)
 {
 	DetachedSong song(uri);
@@ -105,6 +109,9 @@ song_load(TextFile &file, const char *uri,
 			tag.AddItem(type, value);
 		} else if (StringIsEqual(line, "Time")) {
 			tag.SetDuration(SignedSongTime::FromS(ParseDouble(value)));
+		} else if (StringIsEqual(line, "Target")) {
+			if (target_r != nullptr)
+				*target_r = value;
 		} else if (StringIsEqual(line, "Format")) {
 			if (audio_format_r != nullptr) {
 				try {
diff --git a/src/SongSave.hxx b/src/SongSave.hxx
index 579ec6f30..ab3105229 100644
--- a/src/SongSave.hxx
+++ b/src/SongSave.hxx
@@ -44,6 +44,7 @@ song_save(BufferedOutputStream &os, const DetachedSong &song);
  */
 DetachedSong
 song_load(TextFile &file, const char *uri,
+	  std::string *target_r=nullptr,
 	  AudioFormat *audio_format_r=nullptr);
 
 #endif
diff --git a/src/db/plugins/simple/DirectorySave.cxx b/src/db/plugins/simple/DirectorySave.cxx
index 783dcc3f0..1dedba063 100644
--- a/src/db/plugins/simple/DirectorySave.cxx
+++ b/src/db/plugins/simple/DirectorySave.cxx
@@ -161,12 +161,15 @@ directory_load(TextFile &file, Directory &directory)
 			if (directory.FindSong(name) != nullptr)
 				throw FormatRuntimeError("Duplicate song '%s'", name);
 
+			std::string target;
 			auto audio_format = AudioFormat::Undefined();
 			auto detached_song = song_load(file, name,
+						       &target,
 						       &audio_format);
 
 			auto song = std::make_unique<Song>(std::move(detached_song),
 							   directory);
+			song->target = std::move(target);
 			song->audio_format = audio_format;
 
 			directory.AddSong(std::move(song));
diff --git a/src/db/plugins/simple/Song.cxx b/src/db/plugins/simple/Song.cxx
index e71a885e0..f9ac734ae 100644
--- a/src/db/plugins/simple/Song.cxx
+++ b/src/db/plugins/simple/Song.cxx
@@ -51,6 +51,8 @@ Song::Export() const noexcept
 	LightSong dest(filename.c_str(), tag);
 	if (!parent.IsRoot())
 		dest.directory = parent.GetPath();
+	if (!target.empty())
+		dest.real_uri = target.c_str();
 	dest.mtime = mtime;
 	dest.start_time = start_time;
 	dest.end_time = end_time;
diff --git a/src/db/plugins/simple/Song.hxx b/src/db/plugins/simple/Song.hxx
index 17443fd45..9525de6f8 100644
--- a/src/db/plugins/simple/Song.hxx
+++ b/src/db/plugins/simple/Song.hxx
@@ -93,6 +93,15 @@ struct Song {
 	 */
 	std::string filename;
 
+	/**
+	 * If non-empty, then this object does not describe a file
+	 * within the `music_directory`, but some sort of symbolic
+	 * link pointing to this value.  It can be an absolute URI
+	 * (i.e. with URI scheme) or a URI relative to this object
+	 * (which may begin with one or more "../").
+	 */
+	std::string target;
+
 	template<typename F>
 	Song(F &&_filename, Directory &_parent) noexcept
 		:parent(_parent), filename(std::forward<F>(_filename)) {}