diff --git a/libpurple/purpleprotocolcontacts.h b/libpurple/purpleprotocolcontacts.h
--- a/libpurple/purpleprotocolcontacts.h
+++ b/libpurple/purpleprotocolcontacts.h
@@ -65,6 +65,7 @@
 	GTypeInterface parent;
 
 	/*< public >*/
+	guint (*get_minimum_search_length)(PurpleProtocolContacts *protocol_contacts);
 	void (*search_async)(PurpleProtocolContacts *protocol_contacts, PurpleAccount *account, const char *text, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer data);
 	GListModel *(*search_finish)(PurpleProtocolContacts *protocol_contacts, GAsyncResult *result, GError **error);
 
@@ -81,6 +82,37 @@
 G_BEGIN_DECLS
 
 /**
+ * purple_protocol_contacts_implements_search:
+ * @protocol_contacts: The instance.
+ *
+ * Checks if @protocol_contacts implements
+ * [vfunc@ProtocolContacts.search_async] and
+ * [vfunc@ProtocolContacts.search_finish].
+ *
+ * Returns: %TRUE if the search interface is implemented, otherwise %FALSE.
+ *
+ * Since: 3.0
+ */
+PURPLE_AVAILABLE_IN_3_0
+gboolean purple_protocol_contacts_implements_search(PurpleProtocolContacts *protocol_contacts);
+
+/**
+ * purple_protocol_contacts_get_minimum_search_length:
+ * @protocol_contacts: The instance.
+ *
+ * Gets the minimum length of the search term before
+ * [method@ProtocolContacts.search_async] should be called.
+ *
+ * The default implementation returns 3.
+ *
+ * Returns: The minimum length of the search term.
+ *
+ * Since: 3.0
+ */
+PURPLE_AVAILABLE_IN_3_0
+guint purple_protocol_contacts_get_minimum_search_length(PurpleProtocolContacts *protocol_contacts);
+
+/**
  * purple_protocol_contacts_search_async:
  * @protocol_contacts: The instance.
  * @account: The [class@Account] to search under.
# HG changeset patch
# Node ID f17219e4e782
# Parent  efc29eab5d95
diff --git a/libpurple/purpleprotocolcontacts.c b/libpurple/purpleprotocolcontacts.c
--- a/libpurple/purpleprotocolcontacts.c
+++ b/libpurple/purpleprotocolcontacts.c
@@ -25,6 +25,15 @@
 #include "util.h"
 
 /******************************************************************************
+ * Default Implementations
+ *****************************************************************************/
+static guint
+purple_protocol_contacts_default_get_minimum_search_length(G_GNUC_UNUSED PurpleProtocolContacts *protocol_contacts)
+{
+	return 3;
+}
+
+/******************************************************************************
  * GInterface Implementation
  *****************************************************************************/
 G_DEFINE_INTERFACE(PurpleProtocolContacts, purple_protocol_contacts,
@@ -33,11 +42,52 @@
 static void
 purple_protocol_contacts_default_init(G_GNUC_UNUSED PurpleProtocolContactsInterface *iface)
 {
+	iface->get_minimum_search_length = purple_protocol_contacts_default_get_minimum_search_length;
 }
 
 /******************************************************************************
  * Public API
  *****************************************************************************/
+gboolean
+purple_protocol_contacts_implements_search(PurpleProtocolContacts *protocol_contacts)
+{
+	PurpleProtocolContactsInterface *iface = NULL;
+
+	g_return_val_if_fail(PURPLE_IS_PROTOCOL_CONTACTS(protocol_contacts),
+	                     FALSE);
+
+	iface = PURPLE_PROTOCOL_CONTACTS_GET_IFACE(protocol_contacts);
+
+	if(iface->get_minimum_search_length == NULL) {
+		return FALSE;
+	}
+
+	if(iface->search_async == NULL) {
+		return FALSE;
+	}
+
+	if(iface->search_finish == NULL) {
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+guint
+purple_protocol_contacts_get_minimum_search_length(PurpleProtocolContacts *protocol_contacts)
+{
+	PurpleProtocolContactsInterface *iface = NULL;
+
+	g_return_val_if_fail(PURPLE_IS_PROTOCOL_CONTACTS(protocol_contacts), 3);
+
+	iface = PURPLE_PROTOCOL_CONTACTS_GET_IFACE(protocol_contacts);
+	if(iface != NULL && iface->get_minimum_search_length != NULL) {
+		return iface->get_minimum_search_length(protocol_contacts);
+	}
+
+	return 3;
+}
+
 void
 purple_protocol_contacts_search_async(PurpleProtocolContacts *protocol_contacts,
                                       PurpleAccount *account,
diff --git a/libpurple/tests/test_protocol_contacts.c b/libpurple/tests/test_protocol_contacts.c
--- a/libpurple/tests/test_protocol_contacts.c
+++ b/libpurple/tests/test_protocol_contacts.c
@@ -85,6 +85,21 @@
  * TestProtocolContactsEmpty Tests
  *****************************************************************************/
 static void
+test_purple_protocol_contacts_empty_get_minimum_search_length(void) {
+	PurpleProtocolContacts *protocol = NULL;
+	guint minimum_search_length = 0;
+
+	protocol = g_object_new(test_purple_protocol_contacts_empty_get_type(),
+	                        NULL);
+	minimum_search_length = purple_protocol_contacts_get_minimum_search_length(protocol);
+
+	/* There is a default implementation that returns 3. */
+	g_assert_cmpuint(minimum_search_length, ==, 3);
+
+	g_assert_finalize_object(protocol);
+}
+
+static void
 test_purple_protocol_contacts_empty_search_async(void) {
 	if(g_test_subprocess()) {
 		PurpleAccount *account = NULL;
@@ -226,6 +241,13 @@
 	guint get_profile_finish;
 };
 
+static guint
+test_purple_protocol_contacts_get_minimum_search_length(PurpleProtocolContacts *protocol_contacts) {
+	g_assert_true(PURPLE_IS_PROTOCOL_CONTACTS(protocol_contacts));
+
+	return 2;
+}
+
 static void
 test_purple_protocol_contacts_search_async(PurpleProtocolContacts *protocol_contacts,
                                            G_GNUC_UNUSED PurpleAccount *account,
@@ -325,6 +347,8 @@
 
 static void
 test_purple_protocol_contacts_iface_init(PurpleProtocolContactsInterface *iface) {
+	iface->get_minimum_search_length =
+		test_purple_protocol_contacts_get_minimum_search_length;
 	iface->search_async = test_purple_protocol_contacts_search_async;
 	iface->search_finish = test_purple_protocol_contacts_search_finish;
 
@@ -358,6 +382,21 @@
  * TestProtocolContacts search test
  *****************************************************************************/
 static void
+test_protocol_contacts_search_minimum_length(void) {
+	PurpleProtocolContacts *protocol = NULL;
+	guint minimum_search_length = 0;
+
+	protocol = g_object_new(test_purple_protocol_contacts_get_type(), NULL);
+
+	minimum_search_length =
+		test_purple_protocol_contacts_get_minimum_search_length(protocol);
+
+	g_assert_cmpuint(minimum_search_length, ==, 2);
+
+	g_assert_finalize_object(protocol);
+}
+
+static void
 test_purple_protocol_contacts_search_cb(GObject *obj, GAsyncResult *res,
                                         gpointer data)
 {
@@ -576,6 +615,8 @@
 
 	loop = g_main_loop_new(NULL, FALSE);
 
+	g_test_add_func("/protocol-contacts/empty/get-minimum-search-length",
+	                test_purple_protocol_contacts_empty_get_minimum_search_length);
 	g_test_add_func("/protocol-contacts/empty/search-async",
 	                test_purple_protocol_contacts_empty_search_async);
 	g_test_add_func("/protocol-contacts/empty/search-finish",
@@ -589,6 +630,8 @@
 	g_test_add_func("/protocol-contacts/empty/get-menu",
 	                test_purple_protocol_contacts_empty_get_menu);
 
+	g_test_add_func("/protocol-contacts/normal/get-minimum-search-length",
+	                test_protocol_contacts_search_minimum_length);
 	g_test_add_func("/protocol-contacts/normal/search-async-normal",
 	                test_purple_protocol_contacts_search_normal);
 	g_test_add_func("/protocol-contacts/normal/search-async-error",
