diff --git a/NEWS b/NEWS
index 5d5b4c260..2e88507fc 100644
--- a/NEWS
+++ b/NEWS
@@ -1,5 +1,6 @@
 ver 0.23.5 (not yet released)
 * protocol
+  - support relative offsets for "searchadd"
   - fix "searchaddpl" bug (bogus error "Bad position")
 * migrate to PCRE2
 * GCC 12 build fixes
diff --git a/doc/protocol.rst b/doc/protocol.rst
index 69516dc72..e2bb6466d 100644
--- a/doc/protocol.rst
+++ b/doc/protocol.rst
@@ -1222,6 +1222,8 @@ The music database
 
     The ``position`` parameter specifies where the songs will be
     inserted. [#since_0_23]_
+    It can be relative to the current song as in :ref:`addid
+    <command_addid>`. [#since_0_23_5]_
 
 .. _command_searchaddpl:
 
@@ -1659,3 +1661,4 @@ client-to-client messages are local to the current partition.
 .. [#since_0_23_1] Since :program:`MPD` 0.23.1
 .. [#since_0_23_3] Since :program:`MPD` 0.23.3
 .. [#since_0_23_4] Since :program:`MPD` 0.23.4
+.. [#since_0_23_5] Since :program:`MPD` 0.23.5
diff --git a/meson.build b/meson.build
index 39126836f..481270567 100644
--- a/meson.build
+++ b/meson.build
@@ -44,7 +44,7 @@ version_conf = configuration_data()
 version_conf.set_quoted('PACKAGE', meson.project_name())
 version_conf.set_quoted('PACKAGE_NAME', meson.project_name())
 version_conf.set_quoted('VERSION', meson.project_version())
-version_conf.set_quoted('PROTOCOL_VERSION', '0.23.4')
+version_conf.set_quoted('PROTOCOL_VERSION', '0.23.5')
 configure_file(output: 'Version.h', configuration: version_conf)
 
 conf = configuration_data()
diff --git a/src/command/DatabaseCommands.cxx b/src/command/DatabaseCommands.cxx
index 8a96b92ce..e1b311022 100644
--- a/src/command/DatabaseCommands.cxx
+++ b/src/command/DatabaseCommands.cxx
@@ -18,6 +18,7 @@
  */
 
 #include "DatabaseCommands.hxx"
+#include "PositionArg.hxx"
 #include "Request.hxx"
 #include "Partition.hxx"
 #include "db/DatabaseQueue.hxx"
@@ -86,6 +87,20 @@ ParseQueuePosition(Request &args, unsigned queue_length)
 	return queue_length;
 }
 
+static unsigned
+ParseInsertPosition(Request &args, const playlist &playlist)
+{
+	if (args.size >= 2 && StringIsEqual(args[args.size - 2], "position")) {
+		unsigned position = ParseInsertPosition(args.back(), playlist);
+		args.pop_back();
+		args.pop_back();
+		return position;
+	}
+
+	/* append to the end of the queue by default */
+	return playlist.queue.GetLength();
+}
+
 /**
  * Convert all remaining arguments to a #DatabaseSelection.
  *
@@ -160,7 +175,8 @@ handle_match_add(Client &client, Request args, bool fold_case)
 {
 	auto &partition = client.GetPartition();
 	const auto queue_length = partition.playlist.queue.GetLength();
-	const unsigned position = ParseQueuePosition(args, queue_length);
+	const unsigned position =
+		ParseInsertPosition(args, partition.playlist);
 
 	SongFilter filter;
 	const auto selection = ParseDatabaseSelection(args, fold_case, filter);