diff --git a/src/feature/nodelist/microdesc.c b/src/feature/nodelist/microdesc.c
index 3fd0f23fb501cc0da2d95c23a11ad9a290539df6..9e3a82cffcff6d0fa211f575386bf62988df022f 100644
--- a/src/feature/nodelist/microdesc.c
+++ b/src/feature/nodelist/microdesc.c
@@ -915,6 +915,10 @@ microdesc_free_(microdesc_t *md, const char *fname, int lineno)
     tor_free(md->body);
 
   nodefamily_free(md->family);
+  if (md->family_ids) {
+    SMARTLIST_FOREACH(md->family_ids, char *, cp, tor_free(cp));
+    smartlist_free(md->family_ids);
+  }
   short_policy_free(md->exit_policy);
   short_policy_free(md->ipv6_exit_policy);
 
diff --git a/src/feature/nodelist/microdesc_st.h b/src/feature/nodelist/microdesc_st.h
index c642e6e12b58beea96332a0a45d2b2d165280eeb..f8d6b6a27adeb1cd90ce0a149fa4ac9991571991 100644
--- a/src/feature/nodelist/microdesc_st.h
+++ b/src/feature/nodelist/microdesc_st.h
@@ -16,6 +16,7 @@ struct curve25519_public_key_t;
 struct ed25519_public_key_t;
 struct nodefamily_t;
 struct short_policy_t;
+struct smartlist_t;
 
 #include "ext/ht.h"
 
@@ -73,6 +74,11 @@ struct microdesc_t {
   uint16_t ipv6_orport;
   /** As routerinfo_t.family, with readable members parsed. */
   struct nodefamily_t *family;
+  /** A list of strings representing router family IDs.
+   * May be null; Copied from family-ids.
+   * (Happy families only.) */
+  struct smartlist_t *family_ids;
+
   /** IPv4 exit policy summary */
   struct short_policy_t *exit_policy;
   /** IPv6 exit policy summary */
diff --git a/src/feature/nodelist/routerinfo_st.h b/src/feature/nodelist/routerinfo_st.h
index a5c00c85c51605cf24fdec63c43a56e56c21d227..73a44f08f035a08a57d598febc97dd55d7253581 100644
--- a/src/feature/nodelist/routerinfo_st.h
+++ b/src/feature/nodelist/routerinfo_st.h
@@ -15,6 +15,7 @@
 #include "feature/nodelist/signed_descriptor_st.h"
 
 struct curve25519_public_key_t;
+struct smartlist_t;
 
 /** Information about another onion router in the network. */
 struct routerinfo_t {
@@ -67,6 +68,10 @@ struct routerinfo_t {
   long uptime; /**< How many seconds the router claims to have been up */
   smartlist_t *declared_family; /**< Nicknames of router which this router
                                  * claims are its family. */
+  /** A list of strings representing router family IDs.
+   * May be null. Extracted from family-certs.
+   * (Happy families only.) */
+  struct smartlist_t *family_ids;
   char *contact_info; /**< Declared contact info for this router. */
   unsigned int is_hibernating:1; /**< Whether the router claims to be
                                   * hibernating */
diff --git a/src/feature/nodelist/routerlist.c b/src/feature/nodelist/routerlist.c
index 7904f7d03237a2664037e2e59087aa77449f8c72..4dc6b9e7561c3d85253fe18e721248b810f7c034 100644
--- a/src/feature/nodelist/routerlist.c
+++ b/src/feature/nodelist/routerlist.c
@@ -940,6 +940,10 @@ routerinfo_free_(routerinfo_t *router)
     SMARTLIST_FOREACH(router->declared_family, char *, s, tor_free(s));
     smartlist_free(router->declared_family);
   }
+  if (router->family_ids) {
+    SMARTLIST_FOREACH(router->family_ids, char *, cp, tor_free(cp));
+    smartlist_free(router->family_ids);
+  }
   addr_policy_list_free(router->exit_policy);
   short_policy_free(router->ipv6_exit_policy);