diff --git a/ChangeLog.API b/ChangeLog.API
--- a/ChangeLog.API
+++ b/ChangeLog.API
@@ -49,7 +49,6 @@
 		* purple_protocols_remove
 		* purple_protocols_find
 		* purple_protocols_get_all
-		* purple_protocols_get_handle
 		* purple_protocols_init
 		* purple_protocols_uninit
 		* purple_request_certificate
diff --git a/doc/reference/libpurple/libpurple-docs.xml b/doc/reference/libpurple/libpurple-docs.xml
--- a/doc/reference/libpurple/libpurple-docs.xml
+++ b/doc/reference/libpurple/libpurple-docs.xml
@@ -85,6 +85,7 @@
       <xi:include href="xml/purpleprotocolclient.xml" />
       <xi:include href="xml/purpleprotocolfactory.xml" />
       <xi:include href="xml/purpleprotocolim.xml" />
+      <xi:include href="xml/purpleprotocolmanager.xml" />
       <xi:include href="xml/purpleprotocolmedia.xml" />
       <xi:include href="xml/purpleprotocolprivacy.xml" />
       <xi:include href="xml/purpleprotocolserver.xml" />
diff --git a/libpurple/core.c b/libpurple/core.c
--- a/libpurple/core.c
+++ b/libpurple/core.c
@@ -147,7 +147,7 @@
 	}
 
 	purple_cmds_init();
-	purple_protocols_init();
+	purple_protocol_manager_startup();
 
 	purple_credential_manager_startup(); /* before accounts */
 
@@ -246,7 +246,7 @@
 	purple_prefs_uninit();
 	purple_plugins_uninit();
 
-	purple_protocols_uninit();
+	purple_protocol_manager_shutdown();
 
 	purple_cmds_uninit();
 	purple_log_uninit();
diff --git a/libpurple/meson.build b/libpurple/meson.build
--- a/libpurple/meson.build
+++ b/libpurple/meson.build
@@ -63,6 +63,7 @@
 	'purpleprotocolclient.c',
 	'purpleprotocolfactory.c',
 	'purpleprotocolim.c',
+	'purpleprotocolmanager.c',
 	'purpleprotocolmedia.c',
 	'purpleprotocolprivacy.c',
 	'purpleprotocolserver.c',
@@ -154,6 +155,7 @@
 	'purpleprotocolfactory.h',
 	'purpleprotocolim.h',
 	'purpleprotocolmedia.h',
+	'purpleprotocolmanager.h',
 	'purpleprotocolprivacy.h',
 	'purpleprotocolserver.h',
 	'purpleuiinfo.h',
diff --git a/libpurple/protocols.h b/libpurple/protocols.h
--- a/libpurple/protocols.h
+++ b/libpurple/protocols.h
@@ -33,8 +33,6 @@
  * @see_also: <link linkend="chapter-signals-protocol">Protocol signals</link>
  */
 
-#define PURPLE_PROTOCOLS_DOMAIN  (g_quark_from_static_string("protocols"))
-
 /**************************************************************************/
 /* Basic Protocol Information                                             */
 /**************************************************************************/
@@ -419,33 +417,6 @@
  */
 GList *purple_protocols_get_all(void);
 
-/**************************************************************************/
-/* Protocols Subsytem API                                                 */
-/**************************************************************************/
-
-/**
- * purple_protocols_init:
- *
- * Initializes the protocols subsystem.
- */
-void purple_protocols_init(void);
-
-/**
- * purple_protocols_get_handle:
- *
- * Returns the protocols subsystem handle.
- *
- * Returns: The protocols subsystem handle.
- */
-void *purple_protocols_get_handle(void);
-
-/**
- * purple_protocols_uninit:
- *
- * Uninitializes the protocols subsystem.
- */
-void purple_protocols_uninit(void);
-
 G_END_DECLS
 
 #endif /* PURPLE_PROTOCOLS_H */
diff --git a/libpurple/protocols.c b/libpurple/protocols.c
--- a/libpurple/protocols.c
+++ b/libpurple/protocols.c
@@ -32,13 +32,12 @@
 #include "purpleaccountoption.h"
 #include "purplecredentialmanager.h"
 #include "purpleprotocolattention.h"
+#include "purpleprotocolmanager.h"
 #include "purpleprotocolmedia.h"
 #include "purpleprotocolserver.h"
 #include "request.h"
 #include "util.h"
 
-static GHashTable *protocols = NULL;
-
 /**************************************************************************
  * GBoxed code for PurpleProtocolChatEntry
  **************************************************************************/
@@ -273,6 +272,7 @@
 								PurpleStatus *old_status, PurpleStatus *new_status)
 {
 	PurpleProtocol *protocol;
+	PurpleProtocolManager *manager;
 
 	if (purple_status_is_online(new_status) &&
 		purple_account_is_disconnected(account) &&
@@ -307,7 +307,9 @@
 		 */
 		return;
 
-	protocol = purple_protocols_find(purple_account_get_protocol_id(account));
+	manager = purple_protocol_manager_get_default();
+	protocol = purple_protocol_manager_find(manager,
+	                                        purple_account_get_protocol_id(account));
 
 	if (protocol == NULL)
 		return;
@@ -371,6 +373,7 @@
 {
 	PurpleAttentionType *attn;
 	PurpleProtocol *protocol;
+	PurpleProtocolManager *manager;
 	PurpleIMConversation *im;
 	PurpleBuddy *buddy;
 	const char *alias;
@@ -379,7 +382,9 @@
 	g_return_if_fail(gc != NULL);
 	g_return_if_fail(who != NULL);
 
-	protocol = purple_protocols_find(purple_account_get_protocol_id(purple_connection_get_account(gc)));
+	manager = purple_protocol_manager_get_default();
+	protocol = purple_protocol_manager_find(manager,
+	                                        purple_account_get_protocol_id(purple_connection_get_account(gc)));
 	g_return_if_fail(PURPLE_IS_PROTOCOL_ATTENTION(protocol));
 
 	attn = purple_get_attention_type_from_code(purple_connection_get_account(gc), type_code);
@@ -564,41 +569,19 @@
 /**************************************************************************
  * Protocols API
  **************************************************************************/
-/*
- * Negative if a before b, 0 if equal, positive if a after b.
- */
-static gint
-compare_protocol(PurpleProtocol *a, PurpleProtocol *b)
-{
-	const gchar *aname = purple_protocol_get_name(a);
-	const gchar *bname = purple_protocol_get_name(b);
+#define PURPLE_PROTOCOLS_DOMAIN  (g_quark_from_static_string("protocols"))
 
-	if (aname) {
-		if (bname)
-			return strcmp(aname, bname);
-		else
-			return -1;
-	} else {
-		if (bname)
-			return 1;
-		else
-			return 0;
-	}
+PurpleProtocol *
+purple_protocols_find(const char *id) {
+	PurpleProtocolManager *manager = purple_protocol_manager_get_default();
+
+	return purple_protocol_manager_find(manager, id);
 }
 
 PurpleProtocol *
-purple_protocols_find(const char *id)
-{
-	g_return_val_if_fail(protocols != NULL && id != NULL, NULL);
-
-	return g_hash_table_lookup(protocols, id);
-}
-
-PurpleProtocol *
-purple_protocols_add(GType protocol_type, GError **error)
-{
-	PurpleProtocol *protocol;
-	PurpleProtocolClass *klass;
+purple_protocols_add(GType protocol_type, GError **error) {
+	PurpleProtocol *protocol = NULL;
+	PurpleProtocolManager *manager = NULL;
 
 	if (protocol_type == G_TYPE_INVALID) {
 		g_set_error_literal(error, PURPLE_PROTOCOLS_DOMAIN, 0,
@@ -619,121 +602,29 @@
 	}
 
 	protocol = g_object_new(protocol_type, NULL);
-	if (!protocol) {
-		g_set_error_literal(error, PURPLE_PROTOCOLS_DOMAIN, 0,
-		                    _("Could not create protocol instance"));
-		return NULL;
-	}
 
-	if (!purple_protocol_get_id(protocol)) {
-		g_set_error_literal(error, PURPLE_PROTOCOLS_DOMAIN, 0,
-		                    _("Protocol does not provide an ID"));
-
-		g_object_unref(protocol);
-		return NULL;
-	}
-
-	if (purple_protocols_find(purple_protocol_get_id(protocol))) {
-		g_set_error(error, PURPLE_PROTOCOLS_DOMAIN, 0,
-		            _("A protocol with the ID %s is already added."),
-		            purple_protocol_get_id(protocol));
-
-		g_object_unref(protocol);
+	manager = purple_protocol_manager_get_default();
+	if(!purple_protocol_manager_register(manager, protocol, error)) {
 		return NULL;
 	}
 
-	/* Make sure the protocol implements the required functions */
-	klass = PURPLE_PROTOCOL_GET_CLASS(protocol);
-
-	if (!klass->login        || !klass->close    ||
-	    !klass->status_types || !klass->list_icon )
-	{
-		g_set_error(error, PURPLE_PROTOCOLS_DOMAIN, 0,
-		            _("Protocol %s does not implement all the functions in "
-		            "PurpleProtocolClass"), purple_protocol_get_id(protocol));
-
-		g_object_unref(protocol);
-		return NULL;
-	}
-
-	g_hash_table_insert(protocols, g_strdup(purple_protocol_get_id(protocol)),
-	                    protocol);
-
-	purple_debug_info("protocols", "Added protocol %s\n",
-	                  purple_protocol_get_id(protocol));
-
-	purple_signal_emit(purple_protocols_get_handle(), "protocol-added",
-	                   protocol);
 	return protocol;
 }
 
-gboolean purple_protocols_remove(PurpleProtocol *protocol, GError **error)
-{
+gboolean
+purple_protocols_remove(PurpleProtocol *protocol, GError **error) {
+	PurpleProtocolManager *manager;
+
 	g_return_val_if_fail(PURPLE_IS_PROTOCOL(protocol), FALSE);
 	g_return_val_if_fail(purple_protocol_get_id(protocol) != NULL, FALSE);
 
-	if (purple_protocols_find(purple_protocol_get_id(protocol)) == NULL) {
-		g_set_error(error, PURPLE_PROTOCOLS_DOMAIN, 0,
-		            _("Protocol %s is not added."),
-		            purple_protocol_get_id(protocol));
-
-		return FALSE;
-	}
-
-	purple_debug_info("protocols", "Removing protocol %s\n",
-	                  purple_protocol_get_id(protocol));
-
-	purple_signal_emit(purple_protocols_get_handle(), "protocol-removed",
-	                   protocol);
-
-	g_hash_table_remove(protocols, purple_protocol_get_id(protocol));
-	return TRUE;
+	manager = purple_protocol_manager_get_default();
+	return purple_protocol_manager_unregister(manager, protocol, error);
 }
 
 GList *
-purple_protocols_get_all(void)
-{
-	GList *ret = NULL;
-	PurpleProtocol *protocol;
-	GHashTableIter iter;
-
-	g_hash_table_iter_init(&iter, protocols);
-	while (g_hash_table_iter_next(&iter, NULL, (gpointer *)&protocol))
-		ret = g_list_insert_sorted(ret, protocol, (GCompareFunc)compare_protocol);
-
-	return ret;
-}
-
-/**************************************************************************
- * Protocols Subsystem API
- **************************************************************************/
-void
-purple_protocols_init(void)
-{
-	void *handle = purple_protocols_get_handle();
+purple_protocols_get_all(void) {
+	PurpleProtocolManager *manager = purple_protocol_manager_get_default();
 
-	protocols = g_hash_table_new_full(g_str_hash, g_str_equal, g_free,
-			g_object_unref);
-
-	purple_signal_register(handle, "protocol-added",
-	                       purple_marshal_VOID__POINTER,
-	                       G_TYPE_NONE, 1, PURPLE_TYPE_PROTOCOL);
-	purple_signal_register(handle, "protocol-removed",
-	                       purple_marshal_VOID__POINTER,
-	                       G_TYPE_NONE, 1, PURPLE_TYPE_PROTOCOL);
+	return purple_protocol_manager_get_all(manager);
 }
-
-void *
-purple_protocols_get_handle(void)
-{
-	static int handle;
-
-	return &handle;
-}
-
-void
-purple_protocols_uninit(void) 
-{
-	g_hash_table_destroy(protocols);
-}
-
diff --git a/libpurple/purpleprivate.h b/libpurple/purpleprivate.h
--- a/libpurple/purpleprivate.h
+++ b/libpurple/purpleprivate.h
@@ -220,6 +220,20 @@
  */
 void purple_credential_manager_shutdown(void);
 
+/**
+ * purple_protocol_manager_start:
+ *
+ * Starts up the protocol manager by creating the default instance.
+ */
+void purple_protocol_manager_startup(void);
+
+/**
+ * purple_protocol_manager_shutdown:
+ *
+ * Shutdowns down the protocol manager by destroying the default instance.
+ */
+void purple_protocol_manager_shutdown(void);
+
 G_END_DECLS
 
 #endif /* PURPLE_PRIVATE_H */
diff --git a/libpurple/purpleprotocolmanager.h b/libpurple/purpleprotocolmanager.h
new file mode 100644
--- /dev/null
+++ b/libpurple/purpleprotocolmanager.h
@@ -0,0 +1,160 @@
+/*
+ * Purple - Internet Messaging Library
+ * Copyright (C) Pidgin Developers <devel@pidgin.im>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses/>.
+ */
+
+#if !defined(PURPLE_GLOBAL_HEADER_INSIDE) && !defined(PURPLE_COMPILATION)
+# error "only <pidgin.h> may be included directly"
+#endif
+
+#ifndef PURPLE_PROTOCOL_MANAGER_H
+#define PURPLE_PROTOCOL_MANAGER_H
+
+#include <glib.h>
+#include <glib-object.h>
+
+#include "protocol.h"
+
+G_BEGIN_DECLS
+
+/**
+ * SECTION:purpleprotocolmanager
+ * @section_id: libpurple-purpleprotocolmanager
+ * @title: Purple Protocol Manager
+ *
+ * #PurpleProtocolManager keeps track of all protocols and emits signals when
+ * protocols are registered and unregistered.
+ */
+
+/**
+ * PURPLE_PROTOCOL_MANAGER_DOMAIN:
+ *
+ * A #GError domain for errors from #PurpleProtocolManager.
+ *
+ * Since: 3.0.0
+ */
+#define PURPLE_PROTOCOL_MANAGER_DOMAIN (g_quark_from_static_string("purple-protocol-manager"))
+
+/**
+ * PURPLE_TYPE_PROTOCOL_MANAGER:
+ *
+ * The standard _get_type macro for #PurpleProtocolManager.
+ */
+#define PURPLE_TYPE_PROTOCOL_MANAGER (purple_protocol_manager_get_type())
+G_DECLARE_DERIVABLE_TYPE(PurpleProtocolManager, purple_protocol_manager,
+                         PURPLE, PROTOCOL_MANAGER, GObject)
+
+/**
+ * PurpleProtocolManager:
+ *
+ * An opaque data structure that manages protocols.
+ *
+ * Since: 3.0.0
+ */
+
+/**
+ * PurpleProtocolManagerClass:
+ * @registered: The default signal handler for when a protocol is registered.
+ * @unregistered: The default signal handler for when a protocol is
+ *                unregistered.
+ *
+ * The class structure for #PurpleProtocolManager.
+ *
+ * Since: 3.0.0
+ */
+struct _PurpleProtocolManagerClass {
+	/*< private >*/
+	GObjectClass parent;
+
+	/*< public >*/
+	void (*registered)(PurpleProtocolManager *manager, PurpleProtocol *protocol);
+	void (*unregistered)(PurpleProtocolManager *manager, PurpleProtocol *protocol);
+
+	/*< private >*/
+	gpointer reserved[4];
+};
+
+/**
+ * purple_protocol_manager_get_default:
+ *
+ * Gets the default #PurpleProtocolManager instance.
+ *
+ * Returns: (transfer none): The default #PurpleProtocolManager instance.
+ *
+ * Since: 3.0.0
+ */
+PurpleProtocolManager *purple_protocol_manager_get_default(void);
+
+/**
+ * purple_protocol_manager_register:
+ * @manager: The #PurpleProtocolManager instance.
+ * @protocol: The #PurpleProtocol to register.
+ * @error: (out) (optional) (nullable): A return address for a #GError.
+ *
+ * Registers @protocol with @manager.
+ *
+ * Returns: %TRUE if @protocol was successfully registered with @manager,
+ *          %FALSE otherwise.
+ *
+ * Since: 3.0.0
+ */
+gboolean purple_protocol_manager_register(PurpleProtocolManager *manager, PurpleProtocol *protocol, GError **error);
+
+/**
+ * purple_protocol_manager_unregister:
+ * @manager: The #PurpleProtocolManager instance.
+ * @protocol: The #PurpleProtocol to unregister.
+ * @error: (out) (optional) (nullable): A return address for a #GError.
+ *
+ * Unregisters @protocol from @manager.
+ *
+ * Returns: %TRUE if @protocol was successfully unregistered from @manager,
+ *          %FALSE otherwise.
+ *
+ * Since: 3.0.0
+ */
+gboolean purple_protocol_manager_unregister(PurpleProtocolManager *manager, PurpleProtocol *protocol, GError **error);
+
+/**
+ * purple_protocol_manager_find:
+ * @manager: The #PurpleProtocolManager instance.
+ * @id: The id of the #PurpleProtocol to find.
+ *
+ * Gets the #PurpleProtocol identified by @id if found, otherwise %NULL.
+ *
+ * Returns: (transfer none): The #PurpleProtocol identified by @id or %NULL.
+ *
+ * Since: 3.0.0
+ */
+PurpleProtocol *purple_protocol_manager_find(PurpleProtocolManager *manager, const gchar *id);
+
+/**
+ * purple_protocol_manager_get_all:
+ * @manager: The #PurpleProtocolManager instance.
+ *
+ * Gets a sorted list of all #PurpleProtocols that are currently registered in
+ * @manager.
+ *
+ * Returns: (transfer container) (element-type PurpleProtocol): The list
+ *          containing all of the #PurpleProtocols registered with @manager.
+ *
+ * Since: 3.0.0
+ */
+GList *purple_protocol_manager_get_all(PurpleProtocolManager *manager);
+
+G_END_DECLS
+
+#endif /* PURPLE_PROTOCOL_MANAGER_H */
diff --git a/libpurple/purpleprotocolmanager.c b/libpurple/purpleprotocolmanager.c
new file mode 100644
--- /dev/null
+++ b/libpurple/purpleprotocolmanager.c
@@ -0,0 +1,229 @@
+/*
+ * Purple - Internet Messaging Library
+ * Copyright (C) Pidgin Developers <devel@pidgin.im>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <glib/gi18n-lib.h>
+
+#include "purpleprotocolmanager.h"
+#include "purpleprivate.h"
+
+enum {
+	SIG_REGISTERED,
+	SIG_UNREGISTERED,
+	N_SIGNALS,
+};
+static guint signals[N_SIGNALS] = {0, };
+
+typedef struct {
+	GHashTable *protocols;
+} PurpleProtocolManagerPrivate;
+
+G_DEFINE_TYPE_WITH_PRIVATE(PurpleProtocolManager, purple_protocol_manager,
+                           G_TYPE_OBJECT);
+
+static PurpleProtocolManager *default_manager = NULL;
+
+/******************************************************************************
+ * GObject Implementation
+ *****************************************************************************/
+static void
+purple_protocol_manager_finalize(GObject *obj) {
+	PurpleProtocolManager *manager = NULL;
+	PurpleProtocolManagerPrivate *priv = NULL;
+
+	manager = PURPLE_PROTOCOL_MANAGER(obj);
+	priv = purple_protocol_manager_get_instance_private(manager);
+
+	g_clear_pointer(&priv->protocols, g_hash_table_destroy);
+
+	G_OBJECT_CLASS(purple_protocol_manager_parent_class)->finalize(obj);
+}
+
+static void
+purple_protocol_manager_init(PurpleProtocolManager *manager) {
+	PurpleProtocolManagerPrivate *priv = NULL;
+
+	priv = purple_protocol_manager_get_instance_private(manager);
+
+	priv->protocols = g_hash_table_new_full(g_str_hash, g_str_equal, g_free,
+	                                        g_object_unref);
+}
+
+static void
+purple_protocol_manager_class_init(PurpleProtocolManagerClass *klass) {
+	GObjectClass *obj_class = G_OBJECT_CLASS(klass);
+
+	obj_class->finalize = purple_protocol_manager_finalize;
+
+	/**
+	 * PurpleProtocolManager::protocol-registered:
+	 * @manager: The #PurpleProtocolManager instance.
+	 * @protocol: The #PurpleProtocol that was registered.
+	 *
+	 * Emitted after @protocol has been registered in @manager.
+	 *
+	 * Since: 3.0.0
+	 */
+	signals[SIG_REGISTERED] = g_signal_new(
+		"registered",
+		G_OBJECT_CLASS_TYPE(klass),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET(PurpleProtocolManagerClass, registered),
+		NULL,
+		NULL,
+		NULL,
+		G_TYPE_NONE,
+		1,
+		PURPLE_TYPE_PROTOCOL);
+
+	/**
+	 * PurpleProtocolManager::protocol-unregistered:
+	 * @manager: The #PurpleProtocolManager instance.
+	 * @protocol: The #PurpleProtocol that was unregistered.
+	 *
+	 * Emitted after @protocol has been unregistered from @manager.
+	 *
+	 * Since: 3.0.0
+	 */
+	signals[SIG_UNREGISTERED] = g_signal_new(
+		"unregistered",
+		G_OBJECT_CLASS_TYPE(klass),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET(PurpleProtocolManagerClass, unregistered),
+		NULL,
+		NULL,
+		NULL,
+		G_TYPE_NONE,
+		1,
+		PURPLE_TYPE_PROTOCOL);
+}
+
+/******************************************************************************
+ * Private API
+ *****************************************************************************/
+void
+purple_protocol_manager_startup(void) {
+	if(default_manager == NULL) {
+		default_manager = g_object_new(PURPLE_TYPE_PROTOCOL_MANAGER, NULL);
+	}
+}
+
+void
+purple_protocol_manager_shutdown(void) {
+	g_clear_object(&default_manager);
+}
+
+/******************************************************************************
+ * Public API
+ *****************************************************************************/
+PurpleProtocolManager *
+purple_protocol_manager_get_default(void) {
+	return default_manager;
+}
+
+gboolean
+purple_protocol_manager_register(PurpleProtocolManager *manager,
+                                 PurpleProtocol *protocol, GError **error)
+{
+	PurpleProtocolManagerPrivate *priv = NULL;
+	const gchar *id = NULL;
+
+	g_return_val_if_fail(PURPLE_IS_PROTOCOL_MANAGER(manager), FALSE);
+	g_return_val_if_fail(PURPLE_IS_PROTOCOL(protocol), FALSE);
+
+	priv = purple_protocol_manager_get_instance_private(manager);
+
+	id = purple_protocol_get_id(protocol);
+	if(g_hash_table_lookup(priv->protocols, id) != NULL) {
+		g_set_error(error, PURPLE_PROTOCOL_MANAGER_DOMAIN, 0,
+		            _("protocol %s is already registered"), id);
+
+		return FALSE;
+	}
+
+	g_hash_table_insert(priv->protocols, g_strdup(id), g_object_ref(protocol));
+
+	g_signal_emit(G_OBJECT(manager), signals[SIG_REGISTERED], 0, protocol);
+
+	return TRUE;
+}
+
+gboolean
+purple_protocol_manager_unregister(PurpleProtocolManager *manager,
+                                   PurpleProtocol *protocol, GError **error)
+{
+	PurpleProtocolManagerPrivate *priv = NULL;
+	const gchar *id = NULL;
+	gboolean ret = FALSE;
+
+	g_return_val_if_fail(PURPLE_IS_PROTOCOL_MANAGER(manager), FALSE);
+	g_return_val_if_fail(PURPLE_IS_PROTOCOL(protocol), FALSE);
+
+	/* We need to hold a reference on the protocol as typically we will be
+	 * holding the only reference on the protocol when this is called and we
+	 * will need to pass it to the signal emission after it's removed from the
+	 * hash table that'll unref it.
+	 */
+	g_object_ref(G_OBJECT(protocol));
+
+	priv = purple_protocol_manager_get_instance_private(manager);
+	id = purple_protocol_get_id(protocol);
+
+	if(g_hash_table_remove(priv->protocols, id)) {
+		g_signal_emit(G_OBJECT(manager), signals[SIG_UNREGISTERED], 0,
+		              protocol);
+
+		ret = TRUE;
+	} else {
+		g_set_error(error, PURPLE_PROTOCOL_MANAGER_DOMAIN, 0,
+		            _("protocol %s is not registered"), id);
+
+		ret = FALSE;
+	}
+
+	g_object_unref(G_OBJECT(protocol));
+
+	return ret;
+}
+
+PurpleProtocol *
+purple_protocol_manager_find(PurpleProtocolManager *manager, const gchar *id) {
+	PurpleProtocolManagerPrivate *priv = NULL;
+	gpointer value = NULL;
+
+	g_return_val_if_fail(PURPLE_PROTOCOL_MANAGER(manager), NULL);
+
+	priv = purple_protocol_manager_get_instance_private(manager);
+
+	value = g_hash_table_lookup(priv->protocols, id);
+	if(value == NULL) {
+		return NULL;
+	}
+
+	return PURPLE_PROTOCOL(value);
+}
+
+GList *
+purple_protocol_manager_get_all(PurpleProtocolManager *manager) {
+	PurpleProtocolManagerPrivate *priv = NULL;
+
+	g_return_val_if_fail(PURPLE_IS_PROTOCOL_MANAGER(manager), NULL);
+
+	priv = purple_protocol_manager_get_instance_private(manager);
+
+	return g_hash_table_get_values(priv->protocols);
+}
diff --git a/pidgin/pidginprotocolstore.c b/pidgin/pidginprotocolstore.c
--- a/pidgin/pidginprotocolstore.c
+++ b/pidgin/pidginprotocolstore.c
@@ -65,8 +65,13 @@
 
 static void
 pidgin_protocol_store_add_protocols(PidginProtocolStore *store) {
-	g_list_foreach(purple_protocols_get_all(),
-	               pidgin_protocol_store_add_protocol_helper, store);
+	PurpleProtocolManager *manager = purple_protocol_manager_get_default();
+	GList *protocols = NULL;
+
+	protocols = purple_protocol_manager_get_all(manager);
+	g_list_foreach(protocols, pidgin_protocol_store_add_protocol_helper,
+	               store);
+	g_list_free(protocols);
 }
 
 static void
@@ -107,17 +112,18 @@
  * Callbacks
  *****************************************************************************/
 static void
-pidgin_protocol_store_protocol_added_cb(PurpleProtocol *protocol,
-                                        gpointer data)
+pidgin_protocol_store_registered_cb(PurpleProtocolManager *manager,
+                                    PurpleProtocol *protocol, gpointer data)
 {
 	pidgin_protocol_store_add_protocol(PIDGIN_PROTOCOL_STORE(data), protocol);
 }
 
 static void
-pidgin_protocol_store_protocol_removed_cb(PurpleProtocol *protocol,
-                                          gpointer data)
+pidgin_protocol_store_unregistered_cb(PurpleProtocolManager *manager,
+                                      PurpleProtocol *protocol, gpointer data)
 {
-	pidgin_protocol_store_remove_protocol(PIDGIN_PROTOCOL_STORE(data), protocol);
+	pidgin_protocol_store_remove_protocol(PIDGIN_PROTOCOL_STORE(data),
+	                                      protocol);
 }
 
 /******************************************************************************
@@ -127,7 +133,7 @@
 
 static void
 pidgin_protocol_store_init(PidginProtocolStore *store) {
-	gpointer protocols_handle = NULL;
+	PurpleProtocolManager *manager = NULL;
 	GType types[PIDGIN_PROTOCOL_STORE_N_COLUMNS] = {
 		PURPLE_TYPE_PROTOCOL,
 		G_TYPE_STRING,
@@ -144,13 +150,11 @@
 	pidgin_protocol_store_add_protocols(store);
 
 	/* add the signal handlers to dynamically add/remove protocols */
-	protocols_handle = purple_protocols_get_handle();
-	purple_signal_connect(protocols_handle, "protocol-added", store,
-	                      G_CALLBACK(pidgin_protocol_store_protocol_added_cb),
-	                      store);
-	purple_signal_connect(protocols_handle, "protocol-removed", store,
-	                      G_CALLBACK(pidgin_protocol_store_protocol_removed_cb),
-	                      store);
+	manager = purple_protocol_manager_get_default();
+	g_signal_connect(G_OBJECT(manager), "registered",
+	                 G_CALLBACK(pidgin_protocol_store_registered_cb), store);
+	g_signal_connect(G_OBJECT(manager), "unregistered",
+	                 G_CALLBACK(pidgin_protocol_store_unregistered_cb), store);
 }
 
 static void
diff --git a/po/POTFILES.in b/po/POTFILES.in
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -261,6 +261,7 @@
 libpurple/purpleprotocolfactory.c
 libpurple/purpleprotocolim.c
 libpurple/purpleprotocolmedia.c
+libpurple/purpleprotocolmanager.c
 libpurple/purpleprotocolprivacy.c
 libpurple/purpleprotocolserver.c
 libpurple/purpleuiinfo.c
