diff --git a/libpurple/purpleperson.h b/libpurple/purpleperson.h
--- a/libpurple/purpleperson.h
+++ b/libpurple/purpleperson.h
@@ -67,7 +67,7 @@
  *
  * Since: 3.0.0
  */
-const gchar *purple_person_get_id(PurplePerson *person);
+const char *purple_person_get_id(PurplePerson *person);
 
 /**
  * purple_person_get_alias:
@@ -81,7 +81,7 @@
  *
  * Since: 3.0.0
  */
-const gchar *purple_person_get_alias(PurplePerson *person);
+const char *purple_person_get_alias(PurplePerson *person);
 
 /**
  * purple_person_set_alias:
@@ -98,7 +98,7 @@
  *
  * Since: 3.0.0
  */
-void purple_person_set_alias(PurplePerson *person, const gchar *alias);
+void purple_person_set_alias(PurplePerson *person, const char *alias);
 
 /**
  * purple_person_get_avatar_for_display:
@@ -144,6 +144,44 @@
 void purple_person_set_avatar(PurplePerson *person, GdkPixbuf *avatar);
 
 /**
+ * purple_person_get_color:
+ * @person: The instance.
+ *
+ * Gets the custom color that has been set for this person.
+ *
+ * Returns: (nullable): The custom color or %NULL if one is not set.
+ *
+ * Since: 3.0.0
+ */
+const char *purple_person_get_color(PurplePerson *person);
+
+/**
+ * purple_person_set_color:
+ * @person: The instance.
+ * @color: (nullable): The new color.
+ *
+ * Sets the custom color of @person to @color. If @color is %NULL the custom
+ * color is removed.
+ *
+ * Since: 3.0.0
+ */
+void purple_person_set_color(PurplePerson *person, const char *color);
+
+/**
+ * purple_person_get_color_for_display:
+ * @person: The instance.
+ *
+ * Gets the color that should be used for @person. If [property@Person:color]
+ * is set that will be returned, otherwise the result of
+ * [method@ContactInfo.get_color] on the priority contact will be returned.
+ *
+ * Returns: The color to display for @person.
+ *
+ * Since: 3.0.0
+ */
+const char *purple_person_get_color_for_display(PurplePerson *person);
+
+/**
  * purple_person_get_tags:
  * @person: The instance.
  *
# HG changeset patch
# Node ID 59f7e2d57b90
# Parent  2dce52f93848
diff --git a/libpurple/purpleperson.c b/libpurple/purpleperson.c
--- a/libpurple/purpleperson.c
+++ b/libpurple/purpleperson.c
@@ -25,10 +25,12 @@
 struct _PurplePerson {
 	GObject parent;
 
-	gchar *id;
+	char *id;
 
-	gchar *alias;
+	char *alias;
 	GdkPixbuf *avatar;
+	char *color;
+
 	PurpleTags *tags;
 
 	GPtrArray *contacts;
@@ -40,6 +42,8 @@
 	PROP_ALIAS,
 	PROP_AVATAR,
 	PROP_AVATAR_FOR_DISPLAY,
+	PROP_COLOR,
+	PROP_COLOR_FOR_DISPLAY,
 	PROP_TAGS,
 	PROP_NAME_FOR_DISPLAY,
 	PROP_PRIORITY_CONTACT_INFO,
@@ -56,7 +60,7 @@
  * Helpers
  *****************************************************************************/
 static void
-purple_person_set_id(PurplePerson *person, const gchar *id) {
+purple_person_set_id(PurplePerson *person, const char *id) {
 	g_return_if_fail(PURPLE_IS_PERSON(person));
 
 	g_free(person->id);
@@ -98,19 +102,32 @@
 	/* See if the priority contact changed. */
 	new_priority = g_ptr_array_index(person->contacts, 0);
 	if(original_priority != new_priority) {
+		GdkPixbuf *old_avatar = NULL;
+		GdkPixbuf *new_avatar = NULL;
 		GObject *obj = G_OBJECT(person);
+		const char *old_color = NULL;
+		const char *new_color = NULL;
 
 		if(PURPLE_IS_CONTACT_INFO(original_priority)) {
+			old_avatar = purple_contact_info_get_avatar(original_priority);
+			old_color = purple_contact_info_get_color(original_priority);
+
 			g_signal_handlers_disconnect_by_func(original_priority,
 			                                     purple_person_priority_contact_info_notify_cb,
 			                                     person);
 		}
 
 		if(PURPLE_IS_CONTACT_INFO(new_priority)) {
-			g_signal_connect_object(new_priority, "notify::name-for-display",
+			new_avatar = purple_contact_info_get_avatar(new_priority);
+			new_color = purple_contact_info_get_color(new_priority);
+
+			g_signal_connect_object(new_priority, "notify::avatar",
 			                        G_CALLBACK(purple_person_priority_contact_info_notify_cb),
 			                        person, 0);
-			g_signal_connect_object(new_priority, "notify::avatar",
+			g_signal_connect_object(new_priority, "notify::color",
+			                        G_CALLBACK(purple_person_priority_contact_info_notify_cb),
+			                        person, 0);
+			g_signal_connect_object(new_priority, "notify::name-for-display",
 			                        G_CALLBACK(purple_person_priority_contact_info_notify_cb),
 			                        person, 0);
 		}
@@ -119,11 +136,24 @@
 		g_object_notify_by_pspec(obj, properties[PROP_NAME_FOR_DISPLAY]);
 		g_object_notify_by_pspec(obj, properties[PROP_PRIORITY_CONTACT_INFO]);
 
-		/* If the person doesn't have an avatar set, notify that the
-		 * avatar-for-display has changed.
+		/* If the color isn't overridden by the person, check if it has
+		 * changed.
+		 */
+		if(purple_strempty(person->color)) {
+			if(!purple_strequal(old_color, new_color)) {
+				g_object_notify_by_pspec(obj,
+				                         properties[PROP_COLOR_FOR_DISPLAY]);
+			}
+		}
+
+		/* If the person doesn't have an avatar set, check if the avatar
+		 * changed and notify if it has.
 		 */
 		if(!GDK_IS_PIXBUF(person->avatar)) {
-			g_object_notify_by_pspec(obj, properties[PROP_AVATAR_FOR_DISPLAY]);
+			if(old_avatar != new_avatar) {
+				g_object_notify_by_pspec(obj,
+				                         properties[PROP_AVATAR_FOR_DISPLAY]);
+			}
 		}
 
 		g_object_thaw_notify(obj);
@@ -153,12 +183,17 @@
 	const char *property = NULL;
 
 	property = g_param_spec_get_name(pspec);
+
+	g_message("notified for %s", property);
 	if(purple_strequal(property, "name-for-display")) {
 		g_object_notify_by_pspec(G_OBJECT(person),
 		                         properties[PROP_NAME_FOR_DISPLAY]);
 	} else if(purple_strequal(property, "avatar")) {
 		g_object_notify_by_pspec(G_OBJECT(person),
 		                         properties[PROP_AVATAR_FOR_DISPLAY]);
+	} else if(purple_strequal(property, "color")) {
+		g_object_notify_by_pspec(G_OBJECT(person),
+		                         properties[PROP_COLOR_FOR_DISPLAY]);
 	}
 }
 
@@ -236,6 +271,13 @@
 		case PROP_AVATAR_FOR_DISPLAY:
 			g_value_set_object(value, purple_person_get_avatar_for_display(person));
 			break;
+		case PROP_COLOR:
+			g_value_set_string(value, purple_person_get_color(person));
+			break;
+		case PROP_COLOR_FOR_DISPLAY:
+			g_value_set_string(value,
+			                   purple_person_get_color_for_display(person));
+			break;
 		case PROP_TAGS:
 			g_value_set_object(value, purple_person_get_tags(person));
 			break;
@@ -269,6 +311,9 @@
 		case PROP_AVATAR:
 			purple_person_set_avatar(person, g_value_get_object(value));
 			break;
+		case PROP_COLOR:
+			purple_person_set_color(person, g_value_get_string(value));
+			break;
 		default:
 			G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
 			break;
@@ -296,6 +341,7 @@
 
 	g_clear_pointer(&person->id, g_free);
 	g_clear_pointer(&person->alias, g_free);
+	g_clear_pointer(&person->color, g_free);
 
 	G_OBJECT_CLASS(purple_person_parent_class)->finalize(obj);
 }
@@ -385,6 +431,40 @@
 		G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
 
 	/**
+	 * PurplePerson:color:
+	 *
+	 * A custom color to use for this person which will override any colors for
+	 * the contacts that belong to this person.
+	 *
+	 * This is an RGB hex code that user interfaces can use when rendering the
+	 * person.
+	 *
+	 * Since: 3.0.0
+	 */
+	properties[PROP_COLOR] = g_param_spec_string(
+		"color", "color",
+		"The custom color for this person.",
+		NULL,
+		G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+	/**
+	 * PurplePerson:color-for-display:
+	 *
+	 * The color to use to display for this person.
+	 *
+	 * This will return the value of [property@Person:color] if it is set,
+	 * otherwise it well return the value of [property@ContactInfo:color] of
+	 * the priority contact info.
+	 *
+	 * Since: 3.0.0
+	 */
+	properties[PROP_COLOR_FOR_DISPLAY] = g_param_spec_string(
+		"color-for-display", "color-for-display",
+		"The color to use when displaying this person.",
+		NULL,
+		G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+	/**
 	 * PurplePerson:tags:
 	 *
 	 * The [class@Purple.Tags] for this person.
@@ -443,14 +523,14 @@
 	return g_object_new(PURPLE_TYPE_PERSON, NULL);
 }
 
-const gchar *
+const char *
 purple_person_get_id(PurplePerson *person) {
 	g_return_val_if_fail(PURPLE_IS_PERSON(person), NULL);
 
 	return person->id;
 }
 
-const gchar *
+const char *
 purple_person_get_alias(PurplePerson *person) {
 	g_return_val_if_fail(PURPLE_IS_PERSON(person), NULL);
 
@@ -458,7 +538,7 @@
 }
 
 void
-purple_person_set_alias(PurplePerson *person, const gchar *alias) {
+purple_person_set_alias(PurplePerson *person, const char *alias) {
 	g_return_if_fail(PURPLE_IS_PERSON(person));
 
 	if(!purple_strequal(person->alias, alias)) {
@@ -513,6 +593,48 @@
 	}
 }
 
+const char *
+purple_person_get_color(PurplePerson *person) {
+	g_return_val_if_fail(PURPLE_IS_PERSON(person), NULL);
+
+	return person->color;
+}
+
+void
+purple_person_set_color(PurplePerson *person, const char *color) {
+	g_return_if_fail(PURPLE_IS_PERSON(person));
+
+	if(!purple_strequal(person->color, color)) {
+		GObject *obj = G_OBJECT(person);
+
+		g_free(person->color);
+		person->color = g_strdup(color);
+
+		g_object_freeze_notify(obj);
+		g_object_notify_by_pspec(obj, properties[PROP_COLOR]);
+		g_object_notify_by_pspec(obj, properties[PROP_COLOR_FOR_DISPLAY]);
+		g_object_thaw_notify(obj);
+	}
+}
+
+const char *
+purple_person_get_color_for_display(PurplePerson *person) {
+	PurpleContactInfo *priority = NULL;
+
+	g_return_val_if_fail(PURPLE_IS_PERSON(person), NULL);
+
+	if(!purple_strempty(person->color)) {
+		return person->color;
+	}
+
+	priority = purple_person_get_priority_contact_info(person);
+	if(PURPLE_IS_CONTACT_INFO(priority)) {
+		return purple_contact_info_get_color(priority);
+	}
+
+	return NULL;
+}
+
 PurpleTags *
 purple_person_get_tags(PurplePerson *person) {
 	g_return_val_if_fail(PURPLE_IS_PERSON(person), NULL);
diff --git a/libpurple/tests/test_person.c b/libpurple/tests/test_person.c
--- a/libpurple/tests/test_person.c
+++ b/libpurple/tests/test_person.c
@@ -70,6 +70,8 @@
 	GdkPixbuf *avatar_for_display = NULL;
 	char *id = NULL;
 	char *alias = NULL;
+	char *color = NULL;
+	char *color_for_display = NULL;
 	char *name_for_display = NULL;
 
 	/* Create our avatar for testing. */
@@ -83,6 +85,7 @@
 		PURPLE_TYPE_PERSON,
 		"alias", "alias",
 		"avatar", avatar,
+		"color", "#794a85",
 		NULL);
 
 	/* Now use g_object_get to read all of the properties. */
@@ -91,26 +94,38 @@
 		"alias", &alias,
 		"avatar", &avatar1,
 		"avatar-for-display", &avatar_for_display,
+		"color", &color,
+		"color-for-display", &color_for_display,
 		"name-for-display", &name_for_display,
 		"tags", &tags,
 		NULL);
 
-	/* Compare all the things. */
+	/* Test all the things. */
 	g_assert_nonnull(id);
+	g_clear_pointer(&id, g_free);
+
 	g_assert_cmpstr(alias, ==, "alias");
+	g_clear_pointer(&alias, g_free);
+
 	g_assert_true(avatar1 == avatar);
-	g_assert_true(avatar1 == avatar_for_display);
+	g_clear_object(&avatar1);
+
+	g_assert_true(avatar_for_display == avatar_for_display);
+	g_clear_object(&avatar_for_display);
+
+	g_assert_cmpstr(color, ==, "#794a85");
+	g_clear_pointer(&color, g_free);
+
+	g_assert_cmpstr(color_for_display, ==, "#794a85");
+	g_clear_pointer(&color_for_display, g_free);
+
 	g_assert_cmpstr(name_for_display, ==, "alias");
+	g_clear_pointer(&name_for_display, g_free);
+
 	g_assert_nonnull(tags);
-
-	/* Free/unref all the things. */
-	g_clear_pointer(&id, g_free);
-	g_clear_pointer(&alias, g_free);
-	g_clear_object(&avatar1);
-	g_clear_object(&avatar_for_display);
-	g_clear_pointer(&name_for_display, g_free);
 	g_clear_object(&tags);
 
+	/* Additional cleanup. */
 	g_clear_object(&avatar);
 	g_clear_object(&person);
 }
@@ -157,7 +172,9 @@
 	avatar = gdk_pixbuf_new(GDK_COLORSPACE_RGB, FALSE, 8, 1, 1);
 	purple_contact_info_set_avatar(info, avatar);
 	purple_person_add_contact_info(person, info);
+
 	g_assert_cmpuint(called, ==, 1);
+	called = 0;
 
 	/* Make sure the person's alias is overriding the contact info. */
 	g_assert_true(purple_person_get_avatar_for_display(person) == avatar);
@@ -166,7 +183,6 @@
 	/* Now change the avatar on the contact info an verify that we not notified
 	 * of the property changing.
 	 */
-	called = 0;
 	avatar = gdk_pixbuf_new(GDK_COLORSPACE_RGB, FALSE, 8, 1, 1);
 	purple_contact_info_set_avatar(info, avatar);
 	g_assert_cmpuint(called, ==, 1);
@@ -177,6 +193,75 @@
 }
 
 static void
+test_purple_person_color_for_display_person(void) {
+	PurpleContactInfo *info = NULL;
+	PurplePerson *person = NULL;
+	const char *color = NULL;
+	guint color_called = 0;
+	guint color_for_display_called = 0;
+
+	person = purple_person_new();
+	g_signal_connect(person, "notify::color",
+	                 G_CALLBACK(test_purple_person_notify_cb), &color_called);
+	g_signal_connect(person, "notify::color-for-display",
+	                 G_CALLBACK(test_purple_person_notify_cb),
+	                 &color_for_display_called);
+	purple_person_set_color(person, "#abcdef");
+
+	g_assert_cmpuint(color_called, ==, 1);
+	color_called = 0;
+
+	g_assert_cmpuint(color_for_display_called, ==, 1);
+	color_for_display_called = 0;
+
+	/* Make sure the person's color is overriding the contact info. */
+	info = purple_contact_info_new("id");
+	purple_person_add_contact_info(person, info);
+
+	color = purple_person_get_color_for_display(person);
+	g_assert_cmpstr(color, ==, "#abcdef");
+
+	g_assert_cmpuint(color_called, ==, 0);
+	g_assert_cmpuint(color_for_display_called, ==, 0);
+
+	/* Cleanup. */
+	g_clear_object(&info);
+	g_clear_object(&person);
+}
+
+static void
+test_purple_person_color_for_display_contact(void) {
+	PurpleContactInfo *info = NULL;
+	PurplePerson *person = NULL;
+	const char *color = NULL;
+	guint called = 0;
+
+	person = purple_person_new();
+	g_signal_connect(person, "notify::color-for-display",
+	                 G_CALLBACK(test_purple_person_notify_cb), &called);
+
+	info = purple_contact_info_new("id");
+	purple_contact_info_set_color(info, "#012345");
+	purple_person_add_contact_info(person, info);
+
+	g_assert_cmpuint(called, ==, 1);
+	called = 0;
+
+	/* Make sure the person's alias is overriding the contact info. */
+	color = purple_person_get_color_for_display(person);
+	g_assert_cmpstr(color, ==, "#012345");
+
+	/* Now change the avatar on the contact info an verify that we not notified
+	 * of the property changing.
+	 */
+	purple_contact_info_set_color(info, "#6789ab");
+	g_assert_cmpuint(called, ==, 1);
+
+	g_clear_object(&info);
+	g_clear_object(&person);
+}
+
+static void
 test_purple_person_name_for_display_person(void) {
 	PurpleContactInfo *info = NULL;
 	PurplePerson *person = NULL;
@@ -521,6 +606,11 @@
 	g_test_add_func("/person/avatar-for-display/contact",
 	                test_purple_person_avatar_for_display_contact);
 
+	g_test_add_func("/person/color-for-display/person",
+	                test_purple_person_color_for_display_person);
+	g_test_add_func("/person/color-for-display/contact",
+	                test_purple_person_color_for_display_contact);
+
 	g_test_add_func("/person/name-for-display/person",
 	                test_purple_person_name_for_display_person);
 	g_test_add_func("/person/name-for-display/contact",
