diff --git a/NEWS b/NEWS
index 16ad3e322..7c4d36bba 100644
--- a/NEWS
+++ b/NEWS
@@ -1,4 +1,6 @@
 ver 0.21 (not yet released)
+* protocol
+  - "tagtypes" can be used to hide tags
 
 ver 0.20.5 (not yet released)
 * tags
diff --git a/doc/protocol.xml b/doc/protocol.xml
index 7996abd94..8cf783f76 100644
--- a/doc/protocol.xml
+++ b/doc/protocol.xml
@@ -2252,6 +2252,95 @@ OK
             </para>
           </listitem>
         </varlistentry>
+
+        <varlistentry id="command_tagtypes">
+          <term>
+            <cmdsynopsis>
+              <command>tagtypes</command>
+            </cmdsynopsis>
+          </term>
+          <listitem>
+            <para>
+              Shows a list of available tag types.  It is an
+              intersection of the <varname>metadata_to_use</varname>
+              setting and this client's tag mask.
+            </para>
+
+            <para>
+              About the tag mask: each client can decide to disable
+              any number of tag types, which will be omitted from
+              responses to this client.  That is a good idea, because
+              it makes responses smaller.  The following
+              <command>tagtypes</command> sub commands configure this
+              list.
+            </para>
+          </listitem>
+        </varlistentry>
+
+        <varlistentry id="command_tagtypes_disable">
+          <term>
+            <cmdsynopsis>
+              <command>tagtypes</command>
+              <arg choice="plain">disable</arg>
+              <arg choice="req" rep="repeat"><replaceable>NAME</replaceable></arg>
+            </cmdsynopsis>
+          </term>
+          <listitem>
+            <para>
+              Remove one or more tags from the list of tag types the
+              client is interested in.  These will be omitted from
+              responses to this client.
+            </para>
+          </listitem>
+        </varlistentry>
+
+        <varlistentry id="command_tagtypes_enable">
+          <term>
+            <cmdsynopsis>
+              <command>tagtypes</command>
+              <arg choice="plain">enable</arg>
+              <arg choice="req" rep="repeat"><replaceable>NAME</replaceable></arg>
+            </cmdsynopsis>
+          </term>
+          <listitem>
+            <para>
+              Re-enable one or more tags from the list of tag types
+              for this client.  These will no longer be hidden from
+              responses to this client.
+            </para>
+          </listitem>
+        </varlistentry>
+
+        <varlistentry id="command_tagtypes_clear">
+          <term>
+            <cmdsynopsis>
+              <command>tagtypes</command>
+              <arg choice="plain">clear</arg>
+            </cmdsynopsis>
+          </term>
+          <listitem>
+            <para>
+              Clear the list of tag types this client is interested
+              in.  This means that <application>MPD</application> will
+              not send any tags to this client.
+            </para>
+          </listitem>
+        </varlistentry>
+
+        <varlistentry id="command_tagtypes_all">
+          <term>
+            <cmdsynopsis>
+              <command>tagtypes</command>
+              <arg choice="plain">all</arg>
+            </cmdsynopsis>
+          </term>
+          <listitem>
+            <para>
+              Announce that this client is interested in all tag
+              types.  This is the default setting for new clients.
+            </para>
+          </listitem>
+        </varlistentry>
       </variablelist>
     </section>
 
@@ -2410,18 +2499,6 @@ OK
             </para>
           </listitem>
         </varlistentry>
-        <varlistentry id="command_tagtypes">
-          <term>
-            <cmdsynopsis>
-              <command>tagtypes</command>
-            </cmdsynopsis>
-          </term>
-          <listitem>
-            <para>
-              Shows a list of available song metadata.
-            </para>
-          </listitem>
-        </varlistentry>
         <varlistentry id="command_urlhandlers">
           <term>
             <cmdsynopsis>
diff --git a/src/TagPrint.cxx b/src/TagPrint.cxx
index 1406f669b..61d86a12e 100644
--- a/src/TagPrint.cxx
+++ b/src/TagPrint.cxx
@@ -40,8 +40,10 @@ tag_print(Response &r, TagType type, const char *value)
 void
 tag_print_values(Response &r, const Tag &tag)
 {
+	const auto tag_mask = r.GetTagMask();
 	for (const auto &i : tag)
-		tag_print(r, i.type, i.value);
+		if (tag_mask.Test(i.type))
+			tag_print(r, i.type, i.value);
 }
 
 void
diff --git a/src/client/Client.hxx b/src/client/Client.hxx
index 759e611d9..7ce128ce3 100644
--- a/src/client/Client.hxx
+++ b/src/client/Client.hxx
@@ -23,6 +23,7 @@
 #include "check.h"
 #include "ClientMessage.hxx"
 #include "command/CommandListBuilder.hxx"
+#include "tag/Mask.hxx"
 #include "event/FullyBufferedSocket.hxx"
 #include "event/TimeoutMonitor.hxx"
 #include "Compiler.h"
@@ -70,6 +71,11 @@ public:
 	/** idle flags that the client wants to receive */
 	unsigned idle_subscriptions;
 
+	/**
+	 * The tags this client is interested in.
+	 */
+	TagMask tag_mask = TagMask::All();
+
 	/**
 	 * A list of channel names this client is subscribed to.
 	 */
diff --git a/src/client/Response.cxx b/src/client/Response.cxx
index 31eb6a78e..b21ac91b5 100644
--- a/src/client/Response.cxx
+++ b/src/client/Response.cxx
@@ -23,6 +23,12 @@
 #include "util/FormatString.hxx"
 #include "util/AllocatedString.hxx"
 
+TagMask
+Response::GetTagMask() const
+{
+	return GetClient().tag_mask;
+}
+
 bool
 Response::Write(const void *data, size_t length)
 {
diff --git a/src/client/Response.hxx b/src/client/Response.hxx
index 280dc1d55..a133e96a3 100644
--- a/src/client/Response.hxx
+++ b/src/client/Response.hxx
@@ -22,11 +22,13 @@
 
 #include "check.h"
 #include "protocol/Ack.hxx"
+#include "Compiler.h"
 
 #include <stddef.h>
 #include <stdarg.h>
 
 class Client;
+class TagMask;
 
 class Response {
 	Client &client;
@@ -59,6 +61,13 @@ public:
 		return client;
 	}
 
+	/**
+	 * Accessor for Client::tag_mask.  Can be used if caller wants
+	 * to avoid including Client.hxx.
+	 */
+	gcc_pure
+	TagMask GetTagMask() const;
+
 	void SetCommand(const char *_command) {
 		command = _command;
 	}
diff --git a/src/command/AllCommands.cxx b/src/command/AllCommands.cxx
index 7d65898dd..45aed51ad 100644
--- a/src/command/AllCommands.cxx
+++ b/src/command/AllCommands.cxx
@@ -187,7 +187,7 @@ static constexpr struct command commands[] = {
 	{ "subscribe", PERMISSION_READ, 1, 1, handle_subscribe },
 	{ "swap", PERMISSION_CONTROL, 2, 2, handle_swap },
 	{ "swapid", PERMISSION_CONTROL, 2, 2, handle_swapid },
-	{ "tagtypes", PERMISSION_READ, 0, 0, handle_tagtypes },
+	{ "tagtypes", PERMISSION_READ, 0, -1, handle_tagtypes },
 	{ "toggleoutput", PERMISSION_ADMIN, 1, 1, handle_toggleoutput },
 #ifdef ENABLE_DATABASE
 	{ "unmount", PERMISSION_ADMIN, 1, 1, handle_unmount },
diff --git a/src/command/ClientCommands.cxx b/src/command/ClientCommands.cxx
index 3aa89b0e6..c5f70b94d 100644
--- a/src/command/ClientCommands.cxx
+++ b/src/command/ClientCommands.cxx
@@ -24,6 +24,8 @@
 #include "client/Client.hxx"
 #include "client/Response.hxx"
 #include "TagPrint.hxx"
+#include "tag/ParseName.hxx"
+#include "util/StringAPI.hxx"
 
 CommandResult
 handle_close(gcc_unused Client &client, gcc_unused Request args,
@@ -53,10 +55,58 @@ handle_password(Client &client, Request args, Response &r)
 	return CommandResult::OK;
 }
 
-CommandResult
-handle_tagtypes(gcc_unused Client &client, gcc_unused Request request,
-		Response &r)
+static TagMask
+ParseTagMask(Request request)
 {
-	tag_print_types(r);
-	return CommandResult::OK;
+	if (request.IsEmpty())
+		throw ProtocolError(ACK_ERROR_ARG, "Not enough arguments");
+
+	TagMask result = TagMask::None();
+
+	for (const char *name : request) {
+		auto type = tag_name_parse_i(name);
+		if (type == TAG_NUM_OF_ITEM_TYPES)
+			throw ProtocolError(ACK_ERROR_ARG, "Unknown tag type");
+
+		result |= type;
+	}
+
+	return result;
+}
+
+CommandResult
+handle_tagtypes(Client &client, Request request, Response &r)
+{
+	if (request.IsEmpty()) {
+		tag_print_types(r);
+		return CommandResult::OK;
+	}
+
+	const char *cmd = request.shift();
+	if (StringIsEqual(cmd, "all")) {
+		if (!request.IsEmpty()) {
+			r.Error(ACK_ERROR_ARG, "Too many arguments");
+			return CommandResult::ERROR;
+		}
+
+		client.tag_mask = TagMask::All();
+		return CommandResult::OK;
+	} else if (StringIsEqual(cmd, "clear")) {
+		if (!request.IsEmpty()) {
+			r.Error(ACK_ERROR_ARG, "Too many arguments");
+			return CommandResult::ERROR;
+		}
+
+		client.tag_mask = TagMask::None();
+		return CommandResult::OK;
+	} else if (StringIsEqual(cmd, "enable")) {
+		client.tag_mask |= ParseTagMask(request);
+		return CommandResult::OK;
+	} else if (StringIsEqual(cmd, "disable")) {
+		client.tag_mask &= ~ParseTagMask(request);
+		return CommandResult::OK;
+	} else {
+		r.Error(ACK_ERROR_ARG, "Unknown sub command");
+		return CommandResult::ERROR;
+	}
 }
diff --git a/src/command/OtherCommands.cxx b/src/command/OtherCommands.cxx
index 48487fe8e..013d4dff2 100644
--- a/src/command/OtherCommands.cxx
+++ b/src/command/OtherCommands.cxx
@@ -97,7 +97,8 @@ print_tag(TagType type, const char *value, void *ctx)
 {
 	auto &r = *(Response *)ctx;
 
-	tag_print(r, type, value);
+	if (r.GetClient().tag_mask.Test(type))
+		tag_print(r, type, value);
 }
 
 CommandResult
diff --git a/src/db/DatabasePrint.cxx b/src/db/DatabasePrint.cxx
index b5e0b69f7..31f971cd8 100644
--- a/src/db/DatabasePrint.cxx
+++ b/src/db/DatabasePrint.cxx
@@ -196,8 +196,9 @@ PrintUniqueTag(Response &r, TagType tag_type,
 	assert(value != nullptr);
 	tag_print(r, tag_type, value);
 
+	const auto tag_mask = r.GetTagMask();
 	for (const auto &item : tag)
-		if (item.type != tag_type)
+		if (item.type != tag_type && tag_mask.Test(item.type))
 			tag_print(r, item.type, item.value);
 }