diff --git a/libpurple/purpleconversationmanager.h b/libpurple/purpleconversationmanager.h
--- a/libpurple/purpleconversationmanager.h
+++ b/libpurple/purpleconversationmanager.h
@@ -63,6 +63,25 @@
 typedef void (*PurpleConversationManagerForeachFunc)(PurpleConversation *conversation, gpointer data);
 
 /**
+ * purple_conversation_manager_new:
+ * @filename: (nullable): The filename to serialize to.
+ *
+ * Creates a new conversation manager that will be backed by @filename.
+ *
+ * This is typically only called by user interfaces in
+ * [vfunc@Ui.get_conversation_manager].
+ *
+ * If you need a [class@ConversationManager] instance, you probably want to use
+ * [func@ConversationManager.get_default].
+ *
+ * Returns: (transfer full): The new instance.
+ *
+ * Since: 3.0
+ */
+PURPLE_AVAILABLE_IN_3_0
+PurpleConversationManager *purple_conversation_manager_new(const char *filename);
+
+/**
  * purple_conversation_manager_get_default:
  *
  * Gets the default instance of #PurpleConversationManager.  This instance
@@ -76,6 +95,19 @@
 PurpleConversationManager *purple_conversation_manager_get_default(void);
 
 /**
+ * purple_conversation_manager_get_filename:
+ * @manager: The instance.
+ *
+ * Gets the filename the @manager should be serializing itself to.
+ *
+ * Returns: (transfer none) (nullable): The filename.
+ *
+ * Since: 3.0
+ */
+PURPLE_AVAILABLE_IN_3_0
+const char *purple_conversation_manager_get_filename(PurpleConversationManager *manager);
+
+/**
  * purple_conversation_manager_register:
  * @manager: The #PurpleConversationManager instance.
  * @conversation: The #PurpleConversation to register.
# HG changeset patch
# Node ID 49556a7208ac
# Parent  6bed305a8177
diff --git a/libpurple/purpleconversationmanager.c b/libpurple/purpleconversationmanager.c
--- a/libpurple/purpleconversationmanager.c
+++ b/libpurple/purpleconversationmanager.c
@@ -22,11 +22,20 @@
 
 #include "purpleconversationmanager.h"
 
+#include "core.h"
 #include "purplecontact.h"
 #include "purpleprivate.h"
+#include "purpleui.h"
 #include "util.h"
 
 enum {
+	PROP_0,
+	PROP_FILENAME,
+	N_PROPERTIES,
+};
+static GParamSpec *properties[N_PROPERTIES] = {NULL, };
+
+enum {
 	SIG_REGISTERED,
 	SIG_UNREGISTERED,
 	SIG_CONVERSATION_CHANGED,
@@ -38,6 +47,8 @@
 struct _PurpleConversationManager {
 	GObject parent;
 
+	char *filename;
+
 	GHashTable *conversations;
 };
 
@@ -51,6 +62,17 @@
 /******************************************************************************
  * Helpers
  *****************************************************************************/
+static void
+purple_conversation_manager_set_filename(PurpleConversationManager *manager,
+                                         const char *filename)
+{
+	g_return_if_fail(PURPLE_IS_CONVERSATION_MANAGER(manager));
+
+	if(g_set_str(&manager->filename, filename)) {
+		g_object_notify_by_pspec(G_OBJECT(manager), properties[PROP_FILENAME]);
+	}
+}
+
 static gboolean
 purple_conversation_has_id(PurpleConversation *conversation, gpointer data) {
 	const char *needle = data;
@@ -138,15 +160,69 @@
 	PurpleConversationManager *manager = PURPLE_CONVERSATION_MANAGER(obj);
 
 	g_hash_table_destroy(manager->conversations);
+	g_clear_pointer(&manager->filename, g_free);
 
 	G_OBJECT_CLASS(purple_conversation_manager_parent_class)->finalize(obj);
 }
 
 static void
+purple_conversation_manager_get_property(GObject *obj, guint param_id,
+                                         GValue *value, GParamSpec *pspec)
+{
+	PurpleConversationManager *manager = PURPLE_CONVERSATION_MANAGER(obj);
+
+	switch(param_id) {
+	case PROP_FILENAME:
+		g_value_set_string(value,
+		                   purple_conversation_manager_get_filename(manager));
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
+		break;
+	}
+}
+
+static void
+purple_conversation_manager_set_property(GObject *obj, guint param_id,
+                                         const GValue *value,
+                                         GParamSpec *pspec)
+{
+	PurpleConversationManager *manager = PURPLE_CONVERSATION_MANAGER(obj);
+
+	switch(param_id) {
+	case PROP_FILENAME:
+		purple_conversation_manager_set_filename(manager,
+		                                         g_value_get_string(value));
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
+		break;
+	}
+}
+
+static void
 purple_conversation_manager_class_init(PurpleConversationManagerClass *klass) {
 	GObjectClass *obj_class = G_OBJECT_CLASS(klass);
 
 	obj_class->finalize = purple_conversation_manager_finalize;
+	obj_class->get_property = purple_conversation_manager_get_property;
+	obj_class->set_property = purple_conversation_manager_set_property;
+
+	/**
+	 * PurpleConversationManager:filename:
+	 *
+	 * The filename that the manager should save its contents to.
+	 *
+	 * If this is %NULL, no serialization will be performed.
+	 *
+	 * Since: 3.0
+	 */
+	properties[PROP_FILENAME] = g_param_spec_string(
+		"filename", NULL, NULL,
+		NULL,
+		G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+
+	g_object_class_install_properties(obj_class, N_PROPERTIES, properties);
 
 	/**
 	 * PurpleConversationManager::registered:
@@ -249,7 +325,13 @@
 void
 purple_conversation_manager_startup(void) {
 	if(default_manager == NULL) {
-		default_manager = g_object_new(PURPLE_TYPE_CONVERSATION_MANAGER, NULL);
+		PurpleUi *ui = purple_core_get_ui();
+
+		default_manager = purple_ui_get_conversation_manager(ui);
+		if(PURPLE_IS_CONVERSATION_MANAGER(default_manager)) {
+			g_object_add_weak_pointer(G_OBJECT(default_manager),
+			                          (gpointer *)&default_manager);
+		}
 	}
 }
 
@@ -262,10 +344,25 @@
  * Public API
  *****************************************************************************/
 PurpleConversationManager *
+purple_conversation_manager_new(const char *filename) {
+	return g_object_new(
+		PURPLE_TYPE_CONVERSATION_MANAGER,
+		"filename", filename,
+		NULL);
+}
+
+PurpleConversationManager *
 purple_conversation_manager_get_default(void) {
 	return default_manager;
 }
 
+const char *
+purple_conversation_manager_get_filename(PurpleConversationManager *manager) {
+	g_return_val_if_fail(PURPLE_IS_CONVERSATION_MANAGER(manager), NULL);
+
+	return manager->filename;
+}
+
 gboolean
 purple_conversation_manager_register(PurpleConversationManager *manager,
                                      PurpleConversation *conversation)
diff --git a/libpurple/purpleui.h b/libpurple/purpleui.h
--- a/libpurple/purpleui.h
+++ b/libpurple/purpleui.h
@@ -30,6 +30,7 @@
 #include <glib.h>
 #include <glib-object.h>
 
+#include "purpleconversationmanager.h"
 #include "purplehistoryadapter.h"
 #include "purplepresencemanager.h"
 #include "purpleversion.h"
@@ -76,15 +77,32 @@
 	gboolean (*start)(PurpleUi *ui, GError **error);
 	void (*stop)(PurpleUi *ui);
 
-	gpointer (*get_settings_backend)(PurpleUi *ui);
+	PurpleConversationManager *(*get_conversation_manager)(PurpleUi *ui);
 	PurpleHistoryAdapter *(*get_history_adapter)(PurpleUi *ui);
 	PurplePresenceManager *(*get_presence_manager)(PurpleUi *ui);
+	gpointer (*get_settings_backend)(PurpleUi *ui);
 
 	/*< private >*/
 	gpointer reserved[4];
 };
 
 /**
+ * purple_ui_get_conversation_manager:
+ * @ui: The instance.
+ *
+ * Gets the default conversation manager manage from @ui.
+ *
+ * Note: This should only be called by libpurple which will manage it's life
+ * cycle.
+ *
+ * Returns: (transfer full): The conversation manager for @ui.
+ *
+ * Since: 3.0
+ */
+PURPLE_AVAILABLE_IN_3_0
+PurpleConversationManager *purple_ui_get_conversation_manager(PurpleUi *ui);
+
+/**
  * purple_ui_get_id:
  * @ui: The instance.
  *
diff --git a/libpurple/purpleui.c b/libpurple/purpleui.c
--- a/libpurple/purpleui.c
+++ b/libpurple/purpleui.c
@@ -307,6 +307,20 @@
 /******************************************************************************
  * Public API
  *****************************************************************************/
+PurpleConversationManager *
+purple_ui_get_conversation_manager(PurpleUi *ui) {
+	PurpleUiClass *klass = NULL;
+
+	g_return_val_if_fail(PURPLE_IS_UI(ui), NULL);
+
+	klass = PURPLE_UI_GET_CLASS(ui);
+	if(klass != NULL && klass->get_conversation_manager != NULL) {
+		return klass->get_conversation_manager(ui);
+	}
+
+	return NULL;
+}
+
 const char *
 purple_ui_get_id(PurpleUi *ui) {
 	PurpleUiPrivate *priv = NULL;
diff --git a/libpurple/tests/test_conversation_manager.c b/libpurple/tests/test_conversation_manager.c
--- a/libpurple/tests/test_conversation_manager.c
+++ b/libpurple/tests/test_conversation_manager.c
@@ -42,6 +42,36 @@
  * Tests
  *****************************************************************************/
 static void
+test_purple_conversation_manager_new_null(void) {
+	PurpleConversationManager *manager = NULL;
+	const char *filename = NULL;
+
+	manager = purple_conversation_manager_new(NULL);
+
+	g_assert_true(PURPLE_IS_CONVERSATION_MANAGER(manager));
+
+	filename = purple_conversation_manager_get_filename(manager);
+	g_assert_null(filename);
+
+	g_assert_finalize_object(manager);
+}
+
+static void
+test_purple_conversation_manager_new_filename(void) {
+	PurpleConversationManager *manager = NULL;
+	const char *filename = NULL;
+
+	manager = purple_conversation_manager_new("convos.db");
+
+	g_assert_true(PURPLE_IS_CONVERSATION_MANAGER(manager));
+
+	filename = purple_conversation_manager_get_filename(manager);
+	g_assert_cmpstr(filename, ==, "convos.db");
+
+	g_assert_finalize_object(manager);
+}
+
+static void
 test_purple_conversation_manager_register_unregister(void) {
 	PurpleAccount *account = NULL;
 	PurpleConversationManager *manager = NULL;
@@ -336,6 +366,11 @@
 
 	test_ui_purple_init();
 
+	g_test_add_func("/conversation-manager/new/null",
+	                test_purple_conversation_manager_new_null);
+	g_test_add_func("/conversation-manager/new/filename",
+	                test_purple_conversation_manager_new_filename);
+
 	g_test_add_func("/conversation-manager/register-unregister",
 	                test_purple_conversation_manager_register_unregister);
 
diff --git a/libpurple/tests/test_ui.c b/libpurple/tests/test_ui.c
--- a/libpurple/tests/test_ui.c
+++ b/libpurple/tests/test_ui.c
@@ -56,6 +56,11 @@
 	return TRUE;
 }
 
+static PurpleConversationManager *
+test_purple_ui_get_conversation_manager(G_GNUC_UNUSED PurpleUi *ui) {
+	return purple_conversation_manager_new(NULL);
+}
+
 static gpointer
 test_purple_ui_get_settings_backend(G_GNUC_UNUSED PurpleUi *ui) {
 	return g_memory_settings_backend_new();
@@ -70,6 +75,8 @@
 	PurpleUiClass *ui_class = PURPLE_UI_CLASS(klass);
 
 	ui_class->start = test_purple_ui_start;
+	ui_class->get_conversation_manager =
+		test_purple_ui_get_conversation_manager;
 	ui_class->get_settings_backend = test_purple_ui_get_settings_backend;
 }
 
diff --git a/pidgin/pidginui.c b/pidgin/pidginui.c
--- a/pidgin/pidginui.c
+++ b/pidgin/pidginui.c
@@ -147,6 +147,18 @@
 	g_application_quit(g_application_get_default());
 }
 
+static PurpleConversationManager *
+pidgin_ui_get_conversation_manager(G_GNUC_UNUSED PurpleUi *ui) {
+	PurpleConversationManager *manager = NULL;
+	char *filename = NULL;
+
+	filename = g_build_filename(purple_config_dir(), "conversations.db", NULL);
+	manager = purple_conversation_manager_new(filename);
+	g_free(filename);
+
+	return manager;
+}
+
 static gpointer
 pidgin_ui_get_settings_backend(G_GNUC_UNUSED PurpleUi *ui) {
 	GSettingsBackend *backend = NULL;
@@ -202,6 +214,7 @@
 	ui_class->prefs_init = pidgin_ui_prefs_init;
 	ui_class->start = pidgin_ui_start;
 	ui_class->stop = pidgin_ui_stop;
+	ui_class->get_conversation_manager = pidgin_ui_get_conversation_manager;
 	ui_class->get_settings_backend = pidgin_ui_get_settings_backend;
 	ui_class->get_history_adapter = pidgin_ui_get_history_adapter;
 	ui_class->get_presence_manager = pidgin_ui_get_presence_manager;
