diff --git a/libpurple/core.h b/libpurple/core.h
--- a/libpurple/core.h
+++ b/libpurple/core.h
@@ -96,6 +96,22 @@ const char *purple_core_get_version(void
 PurpleCore *purple_get_core(void);
 
 /**
+ * purple_core_get_settings_backend:
+ *
+ * Gets the settings backend to use when saving/loading settings.
+ *
+ * Note, because we do not want to leak `G_SETTINGS_ENABLE_BACKEND` into
+ * libpurple users, this function returns a `gpointer`, and you should cast to
+ * `GSettingsBackend *` after setting `G_SETTINGS_ENABLE_BACKEND` for the files
+ * where you need it.
+ *
+ * Returns: (transfer none): The [class@Gio.SettingsBackend] to use.
+ *
+ * Since: 3.0.0
+ */
+gpointer purple_core_get_settings_backend(void);
+
+/**
  * purple_core_set_ui_ops:
  * @ops: A UI ops structure for the core.
  *
# HG changeset patch
# Node ID 3c38ae3c836d
# Parent  eef0cabd6340
diff --git a/libpurple/core.c b/libpurple/core.c
--- a/libpurple/core.c
+++ b/libpurple/core.c
@@ -20,6 +20,8 @@
  */
 
 #include <glib/gi18n-lib.h>
+#define G_SETTINGS_ENABLE_BACKEND
+#include <gio/gsettingsbackend.h>
 
 #include "internal.h"
 #include "cmds.h"
@@ -56,6 +58,7 @@ struct PurpleCore
 
 static PurpleCoreUiOps *_ops  = NULL;
 static PurpleCore      *_core = NULL;
+static GSettingsBackend *settings_backend = NULL;
 
 static void
 purple_core_print_version(void)
@@ -90,6 +93,8 @@ gboolean
 purple_core_init(PurpleUiInfo *ui_info) {
 	PurpleCoreUiOps *ops;
 	PurpleCore *core;
+	gchar *config_file = NULL;
+	gchar *config = NULL;
 
 	g_return_val_if_fail(PURPLE_IS_UI_INFO(ui_info), FALSE);
 	g_return_val_if_fail(purple_get_core() == NULL, FALSE);
@@ -128,6 +133,11 @@ purple_core_init(PurpleUiInfo *ui_info) 
 	/* The prefs subsystem needs to be initialized before static protocols
 	 * for protocol prefs to work. */
 	purple_prefs_init();
+	config_file = g_strdup_printf("%s.ini", purple_ui_info_get_id(ui_info));
+	config = g_build_filename(purple_config_dir(), config_file, NULL);
+	settings_backend = g_keyfile_settings_backend_new(config, "/", NULL);
+	g_free(config_file);
+	g_free(config);
 
 	purple_debug_init();
 
@@ -237,6 +247,7 @@ purple_core_quit(void)
 		ops->quit();
 
 	/* Everything after prefs_uninit must not try to read any prefs */
+	g_clear_object(&settings_backend);
 	purple_prefs_uninit();
 	purple_plugins_uninit();
 
@@ -286,6 +297,11 @@ purple_get_core(void)
 	return _core;
 }
 
+gpointer
+purple_core_get_settings_backend(void) {
+	return settings_backend;
+}
+
 void
 purple_core_set_ui_ops(PurpleCoreUiOps *ops)
 {
diff --git a/libpurple/data/im.pidgin.Purple.gschema.xml b/libpurple/data/im.pidgin.Purple.gschema.xml
new file mode 100644
--- /dev/null
+++ b/libpurple/data/im.pidgin.Purple.gschema.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<schemalist>
+  <schema path="/" id="im.pidgin.Purple">
+  </schema>
+</schemalist>
diff --git a/libpurple/data/meson.build b/libpurple/data/meson.build
new file mode 100644
--- /dev/null
+++ b/libpurple/data/meson.build
@@ -0,0 +1,25 @@
+PURPLE3_URL_HANDLER_DESKTOP = 'purple3-url-handler.desktop'
+i18n = import('i18n')
+desktop_file_in = configure_file(
+    input : PURPLE3_URL_HANDLER_DESKTOP + '.in.in',
+    output : PURPLE3_URL_HANDLER_DESKTOP + '.in',
+    configuration : conf)
+i18n.merge_file(
+    input : desktop_file_in,
+    output : PURPLE3_URL_HANDLER_DESKTOP,
+    po_dir : meson.project_source_root() / 'po',
+    type : 'desktop',
+    install : true,
+    install_dir : get_option('datadir') / 'applications')
+
+schemas_dir = get_option('prefix') / get_option('datadir') / 'glib-2.0' / 'schemas'
+settings_schemas = [
+  'im.pidgin.Purple.gschema.xml',
+]
+
+install_data(settings_schemas, install_dir: schemas_dir)
+gnome.post_install(glib_compile_schemas: true)
+
+# Compile the schemas in the current directory; this is only useful for testing
+gnome = import('gnome')
+gnome.compile_schemas(depend_files: files(settings_schemas))
diff --git a/libpurple/meson.build b/libpurple/meson.build
--- a/libpurple/meson.build
+++ b/libpurple/meson.build
@@ -363,20 +363,7 @@ libpurple_dep = declare_dependency(
 
 meson.override_dependency(purple_filebase, libpurple_dep)
 
-PURPLE3_URL_HANDLER_DESKTOP = 'purple3-url-handler.desktop'
-i18n = import('i18n')
-desktop_file_in = configure_file(
-    input : 'data' / (PURPLE3_URL_HANDLER_DESKTOP + '.in.in'),
-    output : PURPLE3_URL_HANDLER_DESKTOP + '.in',
-    configuration : conf)
-i18n.merge_file(
-    input : desktop_file_in,
-    output : PURPLE3_URL_HANDLER_DESKTOP,
-    po_dir : meson.project_source_root() / 'po',
-    type : 'desktop',
-    install : true,
-    install_dir : get_option('datadir') / 'applications')
-
+subdir('data')
 subdir('tests')
 subdir('example')
 subdir('plugins')
diff --git a/libpurple/prefs.c b/libpurple/prefs.c
--- a/libpurple/prefs.c
+++ b/libpurple/prefs.c
@@ -1742,7 +1742,6 @@ purple_prefs_uninit()
 	purple_prefs_destroy();
 	g_hash_table_destroy(prefs_hash);
 	prefs_hash = NULL;
-
 }
 
 void
