diff --git a/mirai-console/backend/mirai-console/src/data/AbstractPluginData.kt b/mirai-console/backend/mirai-console/src/data/AbstractPluginData.kt
index a9d20ef5a..011b5aeb1 100644
--- a/mirai-console/backend/mirai-console/src/data/AbstractPluginData.kt
+++ b/mirai-console/backend/mirai-console/src/data/AbstractPluginData.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2019-2021 Mamoe Technologies and contributors.
+ * Copyright 2019-2022 Mamoe Technologies and contributors.
  *
  * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
  * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
@@ -78,9 +78,7 @@ public abstract class AbstractPluginData : PluginData, PluginDataImpl() {
     public final override val updaterSerializer: KSerializer<Unit>
         get() = super.updaterSerializer
 
-    @ConsoleExperimentalApi
-    public override val serializersModule: SerializersModule = EmptySerializersModule
-
+    public override val serializersModule: SerializersModule get() = EmptySerializersModule
     /**
      * 当所属于这个 [PluginData] 的 [Value] 的 [值][Value.value] 被修改时被调用.
      */
diff --git a/mirai-console/backend/mirai-console/src/data/PluginData.kt b/mirai-console/backend/mirai-console/src/data/PluginData.kt
index f9f410994..1aa826fb7 100644
--- a/mirai-console/backend/mirai-console/src/data/PluginData.kt
+++ b/mirai-console/backend/mirai-console/src/data/PluginData.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2019-2021 Mamoe Technologies and contributors.
+ * Copyright 2019-2022 Mamoe Technologies and contributors.
  *
  * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
  * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
@@ -19,7 +19,6 @@ package net.mamoe.mirai.console.data
 
 import kotlinx.serialization.KSerializer
 import kotlinx.serialization.modules.SerializersModule
-import kotlinx.serialization.modules.serializersModuleOf
 import net.mamoe.mirai.console.compiler.common.ResolveContext
 import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.RESTRICTED_NO_ARG_CONSTRUCTOR
 import net.mamoe.mirai.console.data.java.JAutoSavePluginData
@@ -30,6 +29,9 @@ import net.mamoe.mirai.console.plugin.jvm.AbstractJvmPlugin
 import net.mamoe.mirai.console.plugin.jvm.JvmPlugin
 import net.mamoe.mirai.console.plugin.jvm.reloadPluginData
 import net.mamoe.mirai.console.util.ConsoleExperimentalApi
+import net.mamoe.mirai.message.MessageSerializers
+import net.mamoe.mirai.message.data.MessageChain
+import net.mamoe.mirai.utils.NotStableForInheritance
 import kotlin.internal.LowPriorityInOverloadResolution
 import kotlin.reflect.KClass
 import kotlin.reflect.KType
@@ -108,10 +110,13 @@ import kotlin.reflect.typeOf
  * ## 实现注意
  * 此类型处于实验性阶段. 使用其中定义的属性和函数是安全的, 但将来可能会新增成员抽象函数.
  *
+ * 继承 [AbstractPluginData] 比继承 [PluginData] 更安全, 尽管 [AbstractPluginData] 也不稳定.
+ *
  * @see AbstractJvmPlugin.reloadPluginData 通过 [JvmPlugin] 获取指定 [PluginData] 实例.
  * @see PluginDataStorage [PluginData] 存储仓库
  * @see PluginDataExtensions 相关 [SerializerAwareValue] 映射函数
  */
+@NotStableForInheritance
 public interface PluginData {
     /**
      * 这个 [PluginData] 保存时使用的名称.
@@ -130,13 +135,56 @@ public interface PluginData {
     public fun onValueChanged(value: Value<*>)
 
     /**
-     * 用于支持多态序列化.
+     * 序列化本对象数据时使用的 [SerializersModule]. 用于支持多态序列化等.
+     * 在序列化时会先使用 [PluginData.serializersModule], 再对无法找到 serializer 的类型使用 [MessageSerializers.serializersModule].
+     *
+     * ### 使用示例
+     *
+     * 假设你编写了一个类型 `ChatHistory` 用来存储一个群的消息记录:
+     * ```
+     * data class ChatHistory(
+     *     val groupId: Long,
+     *     val chain: List<MessageChain>,
+     * )
+     * ```
+     *
+     * 要在 [PluginData] 中支持它, 需要首先为 `ChatHistory` 编写 [KSerializer].
+     *
+     * 一种方式是为其添加 [kotlinx.serialization.Serializable]:
+     *
+     * ```
+     * @Serializable
+     * data class ChatHistory(
+     *     val groupId: Long,
+     *     val chain: List<MessageChain>,
+     * )
+     * ```
+     *
+     * 编译器将会自动生成一个 [KSerializer], 可通过 `ChatHistory.Companion.serializer()` 获取.
+     *
+     * 然后在 [PluginData] 定义中添加该 [KSerializer]:
+     * ```
+     * object MyData : AutoSavePluginData("save") {
+     *     // 注意, serializersModule 需要早于其他属性定义初始化
+     *     override val serializersModule = SerializersModule {
+     *         contextual(ChatHistory::class, ChatHistory.serializers()) // 为 ChatHistory 指定 KSerializer
+     *     }
+     *
+     *     val histories: Map<Long, ChatHistory> by value()
+     * }
+     * ```
+     *
+     * 然而, 即使不覆盖 `serializersModule` 提供 [KSerializer], mirai 也会通过反射尝试获取.
+     *
+     * 但对于不是使用 `@Serializable` 注解方式, 或者是 `interface`, `abstract class` 等的抽象类型, 则必须覆盖 `serializersModule` 并提供其 [KSerializer].
+     *
+     *
      *
      * @see SerializersModule
-     * @see serializersModuleOf
+     *
+     * @since 2.11
      */
-    @ConsoleExperimentalApi
-    public val serializersModule: SerializersModule
+    public val serializersModule: SerializersModule // 该属性在 2.0 增加, 但在 2.11 才正式支持并删除 @MiraiExperimentalApi
 
     /**
      * 当这个 [PluginData] 被放入一个 [PluginDataStorage] 时调用
@@ -200,28 +248,67 @@ public fun PluginData.value(default: String): SerializerAwareValue<String> = val
 /**
  * 通过具体化类型创建一个 [SerializerAwareValue], 并设置初始值.
  *
- * @param T 具体化参数类型 T. 仅支持:
+ * 2.11 起, 本函数会优先根据返回值推断类型. 如下示例:
+ * ```
+ * var singleMessage: SingleMessage by value(PlainText("str")) // value 的类型为 SerializerAwareValue<SingleMessage>
+ * ```
+ * 这符合正常的类型定义逻辑.
+ *
+ * @param T 具体化参数类型 T. 在 2.11 以前, 支持:
  * - 基础数据类型
  * - 标准库集合类型 ([List], [Map], [Set])
  * - 标准库数据类型 ([Map.Entry], [Pair], [Triple])
- * - 和使用 [kotlinx.serialization](https://github.com/Kotlin/kotlinx.serialization) 的 [Serializable] 标记的
+ * - 使用 [kotlinx.serialization](https://github.com/Kotlin/kotlinx.serialization) 的 [Serializable] 标记的, 可以通过反射获取 [KSerializer] 的类型
+ *
+ * 2.11 起, 还支持:
+ * - [MessageSerializers] 支持的所有类型, 如 [MessageChain].
+ * - 在 [PluginData.serializersModule] 自定义支持的类型
  */
 @Suppress("UNCHECKED_CAST")
 @LowPriorityInOverloadResolution
 public inline fun <reified T> PluginData.value(
     default: T,
     crossinline apply: T.() -> Unit = {},
-): SerializerAwareValue<T> =
-    valueFromKType(typeOf<T>(), default).also { it.value.apply() }
+): SerializerAwareValue<@kotlin.internal.Exact T> {
+    /*
+     * 使用 `@Exact` 的 trick (自 2.11.0-RC)
+     *
+     * 使用前:
+     *
+     * ```
+     * var singleMessage: SingleMessage by value(PlainText("str"))
+     * ```
+     *
+     * `value` 的 reified [T] 根据其参数推断为 [PlainText], 则会使用 `PlainText.serializer()`.
+     *
+     * 可以通过序列化后的 YAML 文本直观地感受问题:
+     * ```yaml
+     * singleMessage:
+     *   content: str
+     * ```
+     * 那么将来若 `singleMessage` 的值变更为非 [PlainText] 类型, 将会无法序列化.
+     *
+     * 附使用 `@Exact` 时的正确结果 (使用 [SingleMessage] 的序列化器 (来自 [MessageSerializers.serializersModule])):
+     *
+     * ```yaml
+     * singleMessage:
+     *   type: PlainText
+     *   value:
+     *     content: str
+     * ```
+     *
+     * 相关测试: [net.mamoe.mirai.console.data.PluginDataTest.supports message chain]
+     */
+    return valueFromKType(typeOf<T>(), default).also { it.value.apply() }
+}
 
 /**
  * 通过具体化类型创建一个 [SerializerAwareValue].
- * @see valueFromKType 查看更多实现信息
  */
 @ResolveContext(RESTRICTED_NO_ARG_CONSTRUCTOR)
 @LowPriorityInOverloadResolution
 public inline fun <@ResolveContext(RESTRICTED_NO_ARG_CONSTRUCTOR) reified T>
-        PluginData.value(apply: T.() -> Unit = {}): SerializerAwareValue<T> =
+        PluginData.value(apply: T.() -> Unit = {}): SerializerAwareValue<@kotlin.internal.Exact T> =
     valueImpl<T>(typeOf<T>(), T::class).also { it.value.apply() }
 
 @Suppress("UNCHECKED_CAST")
diff --git a/mirai-console/backend/mirai-console/src/internal/data/MultiFilePluginDataStorageImpl.kt b/mirai-console/backend/mirai-console/src/internal/data/MultiFilePluginDataStorageImpl.kt
index acca0be75..721fe920f 100644
--- a/mirai-console/backend/mirai-console/src/internal/data/MultiFilePluginDataStorageImpl.kt
+++ b/mirai-console/backend/mirai-console/src/internal/data/MultiFilePluginDataStorageImpl.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2019-2021 Mamoe Technologies and contributors.
+ * Copyright 2019-2022 Mamoe Technologies and contributors.
  *
  * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
  * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
@@ -10,12 +10,14 @@
 package net.mamoe.mirai.console.internal.data
 
 import kotlinx.serialization.json.Json
-import net.mamoe.mirai.console.data.*
+import kotlinx.serialization.modules.plus
+import net.mamoe.mirai.console.data.MultiFilePluginDataStorage
+import net.mamoe.mirai.console.data.PluginData
+import net.mamoe.mirai.console.data.PluginDataHolder
+import net.mamoe.mirai.console.data.PluginDataStorage
 import net.mamoe.mirai.console.util.ConsoleExperimentalApi
+import net.mamoe.mirai.message.MessageSerializers
 import net.mamoe.mirai.utils.MiraiLogger
-import net.mamoe.mirai.utils.SilentLogger
-import net.mamoe.mirai.utils.debug
-import net.mamoe.mirai.utils.warning
 import net.mamoe.yamlkt.Yaml
 import java.io.File
 import java.nio.file.Path
@@ -23,7 +25,7 @@ import java.nio.file.Path
 @Suppress("RedundantVisibilityModifier") // might be public in the future
 internal open class MultiFilePluginDataStorageImpl(
     public final override val directoryPath: Path,
-    private val logger: MiraiLogger = SilentLogger,
+    private val logger: MiraiLogger = MiraiLogger.Factory.create(MultiFilePluginDataStorageImpl::class),
 ) : PluginDataStorage, MultiFilePluginDataStorage {
     init {
         directoryPath.mkdir()
@@ -35,12 +37,15 @@ internal open class MultiFilePluginDataStorageImpl(
         // 0xFEFF is BOM, handle UTF8-BOM
         val text = getPluginDataFile(holder, instance).readText().removePrefix("\uFEFF")
         if (text.isNotBlank()) {
-            logger.warning { "Deserializing $text" }
-            Yaml.decodeFromString(instance.updaterSerializer, text)
+            createYaml(instance).decodeFromString(instance.updaterSerializer, text)
         } else {
             this.store(holder, instance) // save an initial copy
         }
-        logger.debug { "Successfully loaded PluginData: ${instance.saveName} (containing ${instance.castOrNull<AbstractPluginData>()?.valueNodes?.size} properties)" }
+//        logger.verbose { "Successfully loaded PluginData: ${instance.saveName} (containing ${instance.castOrNull<AbstractPluginData>()?.valueNodes?.size} properties)" }
+    }
+
+    internal fun getPluginDataFileInternal(holder: PluginDataHolder, instance: PluginData): File {
+        return getPluginDataFile(holder, instance)
     }
 
     protected open fun getPluginDataFile(holder: PluginDataHolder, instance: PluginData): File {
@@ -56,39 +61,45 @@ internal open class MultiFilePluginDataStorageImpl(
         if (file.isDirectory) {
             error("Target File $file is occupied by a directory therefore data ${instance::class.qualifiedNameOrTip} can't be saved.")
         }
-        logger.debug { "File allocated for ${instance.saveName}: $file" }
+//        logger.verbose { "File allocated for ${instance.saveName}: $file" }
         return file.toFile().also { it.createNewFile() }
     }
 
-    private val json = Json {
-        prettyPrint = true
-        ignoreUnknownKeys = true
-        isLenient = true
-        allowStructuredMapKeys = true
-        encodeDefaults = true
-    }
-
-    private val yaml = Yaml
-
     @ConsoleExperimentalApi
     public override fun store(holder: PluginDataHolder, instance: PluginData) {
         getPluginDataFile(holder, instance).writeText(
             kotlin.runCatching {
-                yaml.encodeToString(instance.updaterSerializer, Unit).also {
-                    yaml.decodeAnyFromString(it) // test yaml
+                createYaml(instance).encodeToString(instance.updaterSerializer, Unit).also {
+                    Yaml.decodeAnyFromString(it) // test yaml
                 }
             }.recoverCatching {
                 logger.warning(
                     "Could not save ${instance.saveName} in YAML format due to exception in YAML encoder. " +
-                            "Please report this exception and relevant configurations to https://github.com/mamoe/mirai-console/issues/new",
+                            "Please report this exception and relevant configurations to https://github.com/mamoe/mirai/issues/new/choose",
                     it
                 )
-                json.encodeToString(instance.updaterSerializer, Unit)
+                @Suppress("JSON_FORMAT_REDUNDANT")
+                Json {
+                    serializersModule = MessageSerializers.serializersModule + instance.serializersModule
+
+                    prettyPrint = true
+                    ignoreUnknownKeys = true
+                    isLenient = true
+                    allowStructuredMapKeys = true
+                    encodeDefaults = true
+                }.encodeToString(instance.updaterSerializer, Unit)
             }.getOrElse {
                 throw IllegalStateException("Exception while saving $instance, saveName=${instance.saveName}", it)
             }
         )
-        logger.debug { "Successfully saved PluginData: ${instance.saveName} (containing ${instance.castOrNull<AbstractPluginData>()?.valueNodes?.size} properties)" }
+//        logger.verbose { "Successfully saved PluginData: ${instance.saveName} (containing ${instance.castOrNull<AbstractPluginData>()?.valueNodes?.size} properties)" }
+    }
+
+    private fun createYaml(instance: PluginData): Yaml {
+        return Yaml {
+            this.serializersModule =
+                MessageSerializers.serializersModule + instance.serializersModule // MessageSerializers.serializersModule is dynamic
+        }
     }
 }
 
diff --git a/mirai-console/backend/mirai-console/src/internal/data/serializerHelper.kt b/mirai-console/backend/mirai-console/src/internal/data/serializerHelper.kt
index 995519d1b..9a01ccd82 100644
--- a/mirai-console/backend/mirai-console/src/internal/data/serializerHelper.kt
+++ b/mirai-console/backend/mirai-console/src/internal/data/serializerHelper.kt
@@ -1,29 +1,29 @@
 /*
- * Copyright 2019-2021 Mamoe Technologies and contributors.
+ * Copyright 2019-2022 Mamoe Technologies and contributors.
  *
- *  此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
- *  Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
+ * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
+ * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
  *
- *  https://github.com/mamoe/mirai/blob/master/LICENSE
+ * https://github.com/mamoe/mirai/blob/dev/LICENSE
  */
 
 @file:Suppress("unused")
 
 package net.mamoe.mirai.console.internal.data
 
-import kotlinx.serialization.ExperimentalSerializationApi
-import kotlinx.serialization.InternalSerializationApi
 import kotlinx.serialization.KSerializer
-import kotlinx.serialization.builtins.*
+import kotlinx.serialization.builtins.ArraySerializer
+import kotlinx.serialization.builtins.nullable
 import kotlinx.serialization.descriptors.SerialDescriptor
 import kotlinx.serialization.encoding.Decoder
 import kotlinx.serialization.encoding.Encoder
+import kotlinx.serialization.modules.SerializersModule
 import kotlinx.serialization.serializer
+import kotlinx.serialization.serializerOrNull
+import net.mamoe.mirai.message.MessageSerializers
 import net.mamoe.yamlkt.YamlDynamicSerializer
 import net.mamoe.yamlkt.YamlNullableDynamicSerializer
 import java.lang.reflect.Modifier
-import java.util.concurrent.ConcurrentHashMap
-import java.util.concurrent.ConcurrentMap
 import kotlin.reflect.KClass
 import kotlin.reflect.KType
 
@@ -32,41 +32,22 @@ import kotlin.reflect.KType
  * Copied from kotlinx.serialization, modifications are marked with "/* mamoe modify */"
  * Copyright 2017-2020 JetBrains s.r.o.
  */
-@OptIn(InternalSerializationApi::class, ExperimentalSerializationApi::class)
-@Suppress(
-    "UNCHECKED_CAST",
-    "NO_REFLECTION_IN_CLASS_PATH",
-    "UNSUPPORTED",
-    "INVISIBLE_MEMBER",
-    "INVISIBLE_REFERENCE",
-    "IMPLICIT_CAST_TO_ANY"
-)
-internal fun serializerMirai(type: KType): KSerializer<Any?> {
-    fun serializerByKTypeImpl(type: KType): KSerializer<Any> {
+@Suppress("UNCHECKED_CAST")
+internal fun SerializersModule.serializerMirai(type: KType): KSerializer<Any?> {
+    fun serializerByKTypeImpl(type: KType): KSerializer<*> {
         val rootClass = type.classifierAsKClass()
 
+        this.serializerOrNull(type)?.let { return it } // Kotlin builtin and user-defined
+        MessageSerializers.serializersModule.serializerOrNull(type)?.let { return it } // Mirai Messages
+
         val typeArguments = type.arguments
             .map { requireNotNull(it.type) { "Star projections in type arguments are not allowed, but had $type" } }
         return when {
-            typeArguments.isEmpty() -> rootClass.serializer()
+            typeArguments.isEmpty() -> this.serializer(type)
             else -> {
-                val serializers = typeArguments
-                    .map(::serializerMirai)
-                // Array is not supported, see KT-32839
+                val serializers = typeArguments.map(::serializerMirai)
                 when (rootClass) {
-                    List::class, MutableList::class, ArrayList::class -> ListSerializer(serializers[0])
-                    HashSet::class -> SetSerializer(serializers[0])
-                    Set::class, MutableSet::class, LinkedHashSet::class -> SetSerializer(serializers[0])
-                    HashMap::class -> MapSerializer(serializers[0], serializers[1])
-                    ConcurrentHashMap::class, ConcurrentMap::class -> MapSerializer(serializers[0], serializers[1]).map(
-                        serializer = { HashMap(it as Map<*, *>) },
-                        deserializer = { ConcurrentHashMap(it) }
-                    )
-                    Map::class, MutableMap::class, LinkedHashMap::class -> MapSerializer(serializers[0], serializers[1])
-                    Map.Entry::class -> MapEntrySerializer(serializers[0], serializers[1])
-                    Pair::class -> PairSerializer(serializers[0], serializers[1])
-                    Triple::class -> TripleSerializer(serializers[0], serializers[1], serializers[2])
-                    /* mamoe modify */ Any::class -> if (type.isMarkedNullable) YamlNullableDynamicSerializer else YamlDynamicSerializer
+                    Any::class -> if (type.isMarkedNullable) YamlNullableDynamicSerializer else YamlDynamicSerializer
                     else -> {
                         if (rootClass.java.isArray) {
                             return ArraySerializer(
@@ -81,10 +62,10 @@ internal fun serializerMirai(type: KType): KSerializer<Any?> {
                     }
                 }
             }
-        }.cast()
+        }
     }
 
-    val result = serializerByKTypeImpl(type)
+    val result = serializerByKTypeImpl(type) as KSerializer<Any>
     return if (type.isMarkedNullable) result.nullable else result.cast()
 }
 
diff --git a/mirai-console/backend/mirai-console/src/internal/data/valueFromKTypeImpl.kt b/mirai-console/backend/mirai-console/src/internal/data/valueFromKTypeImpl.kt
index 23ba837aa..a254998bf 100644
--- a/mirai-console/backend/mirai-console/src/internal/data/valueFromKTypeImpl.kt
+++ b/mirai-console/backend/mirai-console/src/internal/data/valueFromKTypeImpl.kt
@@ -1,10 +1,10 @@
 /*
- * Copyright 2019-2021 Mamoe Technologies and contributors.
+ * Copyright 2019-2022 Mamoe Technologies and contributors.
  *
- *  此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
- *  Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
+ * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
+ * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
  *
- *  https://github.com/mamoe/mirai/blob/master/LICENSE
+ * https://github.com/mamoe/mirai/blob/dev/LICENSE
  */
 
 @file:Suppress("NOTHING_TO_INLINE", "INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
@@ -68,7 +68,7 @@ internal fun PluginData.valueFromKTypeImpl(type: KType): SerializerAwareValue<*>
                     },
                     kToValue = { k -> valueFromKType(type.arguments[0].type!!, k) },
                     vToValue = { v -> valueFromKType(type.arguments[1].type!!, v) }
-                ).serializableValueWith(serializerMirai(type) as KSerializer<Map<Any?, Any?>>) // erased
+                ).serializableValueWith(serializersModule.serializerMirai(type) as KSerializer<Map<Any?, Any?>>) // erased
             }
         }
         MutableList::class,
@@ -84,7 +84,7 @@ internal fun PluginData.valueFromKTypeImpl(type: KType): SerializerAwareValue<*>
                 TODO()
             } else {
                 return createCompositeListValueImpl<Any?> { v -> valueFromKType(type.arguments[0].type!!, v) }
-                    .serializableValueWith(serializerMirai(type) as KSerializer<List<Any?>>)
+                    .serializableValueWith(serializersModule.serializerMirai(type) as KSerializer<List<Any?>>)
             }
         }
         MutableSet::class,
@@ -101,11 +101,11 @@ internal fun PluginData.valueFromKTypeImpl(type: KType): SerializerAwareValue<*>
                 TODO()
             } else {
                 return createCompositeSetValueImpl<Any?> { v -> valueFromKType(type.arguments[0].type!!, v) }
-                    .serializableValueWith(serializerMirai(type) as KSerializer<Set<Any?>>)
+                    .serializableValueWith(serializersModule.serializerMirai(type) as KSerializer<Set<Any?>>)
             }
         }
         else -> {
-            val serializer = serializerMirai(type)
+            val serializer = serializersModule.serializerMirai(type)
             return LazyReferenceValueImpl<Any?>().serializableValueWith(serializer)
         }
     }
diff --git a/mirai-console/backend/mirai-console/test/data/SettingTest.kt b/mirai-console/backend/mirai-console/test/data/PluginDataTest.kt
similarity index 52%
rename from mirai-console/backend/mirai-console/test/data/SettingTest.kt
rename to mirai-console/backend/mirai-console/test/data/PluginDataTest.kt
index e627593f6..8bf0b6a27 100644
--- a/mirai-console/backend/mirai-console/test/data/SettingTest.kt
+++ b/mirai-console/backend/mirai-console/test/data/PluginDataTest.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2019-2021 Mamoe Technologies and contributors.
+ * Copyright 2019-2022 Mamoe Technologies and contributors.
  *
  * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
  * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
@@ -9,14 +9,26 @@
 
 package net.mamoe.mirai.console.data
 
+import kotlinx.serialization.builtins.serializer
 import kotlinx.serialization.json.Json
-import net.mamoe.mirai.console.util.ConsoleInternalApi
+import kotlinx.serialization.modules.SerializersModule
+import net.mamoe.mirai.console.internal.data.MultiFilePluginDataStorageImpl
+import net.mamoe.mirai.console.testFramework.AbstractConsoleInstanceTest
+import net.mamoe.mirai.message.data.MessageChain
+import net.mamoe.mirai.message.data.PlainText
+import net.mamoe.mirai.message.data.SingleMessage
+import net.mamoe.mirai.message.data.messageChainOf
+import net.mamoe.mirai.utils.mapPrimitive
 import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.io.TempDir
+import java.nio.file.Path
 import kotlin.test.assertEquals
 import kotlin.test.assertSame
 
-@OptIn(ConsoleInternalApi::class)
-internal class PluginDataTest {
+internal class PluginDataTest : AbstractConsoleInstanceTest() {
+    @TempDir
+    lateinit var tempDir: Path
+
     class MyPluginData : AutoSavePluginData("test") {
         var int by value(1)
         val map: MutableMap<String, String> by value()
@@ -128,4 +140,86 @@ internal class PluginDataTest {
 
         assertSame(reference(), delegation()) // check shadowing
     }
+
+
+    class SupportsMessageChain : AutoSavePluginData("test") {
+        val chain: MessageChain by value(messageChainOf(PlainText("str")))
+    }
+
+    @Test
+    fun `supports message chain`() {
+        assertEquals(
+            """
+            chain: 
+              - type: PlainText
+                value: 
+                  content: str
+        """.trimIndent(), serializePluginData(SupportsMessageChain())
+        )
+        serializeAndRereadPluginData(SupportsMessageChain())
+    }
+
+    class SupportsPolymorphicCorrectly : AutoSavePluginData("test") {
+        val singleMessage: SingleMessage by value(PlainText("str"))
+        val plainText: PlainText by value(PlainText("str"))
+    }
+
+    @Test
+    fun `supports polymorphic correctly`() {
+        assertEquals(
+            """
+            singleMessage: 
+              type: PlainText
+              value: 
+                content: str
+            plainText: 
+              content: str
+        """.trimIndent(), serializePluginData(SupportsPolymorphicCorrectly())
+        )
+        serializeAndRereadPluginData(SupportsPolymorphicCorrectly())
+    }
+
+    class SupportsSerializersModule : AutoSavePluginData("test") {
+        override val serializersModule: SerializersModule = SerializersModule {
+            contextual(MyClass::class, myClassSerializer)
+        }
+
+        val v: MyClass by value(MyClass("test"))
+
+        data class MyClass(
+            val str: String
+        )
+
+        companion object {
+            private val myClassSerializer = String.serializer().mapPrimitive("MyClass",
+                { MyClass(it) },
+                { it.str }
+            )
+        }
+    }
+
+    @Test
+    fun `supports serializers module`() {
+        assertEquals(
+            """
+            v: test
+        """.trimIndent(), serializePluginData(SupportsSerializersModule())
+        )
+        serializeAndRereadPluginData(SupportsSerializersModule())
+    }
+
+
+    private fun serializePluginData(data: PluginData): String {
+        val storage = MultiFilePluginDataStorageImpl(tempDir)
+        storage.store(mockPlugin, data)
+        return storage.getPluginDataFileInternal(mockPlugin, data).readText()
+    }
+
+    private fun serializeAndRereadPluginData(data: PluginData) {
+        val storage = MultiFilePluginDataStorageImpl(tempDir)
+        storage.store(mockPlugin, data)
+        val serialized = storage.getPluginDataFileInternal(mockPlugin, data).readText()
+        storage.load(mockPlugin, data)
+        assertEquals(serialized, storage.getPluginDataFileInternal(mockPlugin, data).readText())
+    }
 }
diff --git a/mirai-console/tools/intellij-plugin/run/projects/test-project/src/main/kotlin/org/example/myplugin/UsingDerivedMap.kt b/mirai-console/tools/intellij-plugin/run/projects/test-project/src/main/kotlin/org/example/myplugin/UsingDerivedMap.kt
index 728287321..712e3968a 100644
--- a/mirai-console/tools/intellij-plugin/run/projects/test-project/src/main/kotlin/org/example/myplugin/UsingDerivedMap.kt
+++ b/mirai-console/tools/intellij-plugin/run/projects/test-project/src/main/kotlin/org/example/myplugin/UsingDerivedMap.kt
@@ -1,10 +1,10 @@
 /*
- * Copyright 2019-2021 Mamoe Technologies and contributors.
+ * Copyright 2019-2022 Mamoe Technologies and contributors.
  *
- *  此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
- *  Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
+ * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
+ * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
  *
- *  https://github.com/mamoe/mirai/blob/master/LICENSE
+ * https://github.com/mamoe/mirai/blob/dev/LICENSE
  */
 
 package org.example.myplugin
@@ -12,7 +12,10 @@ package org.example.myplugin
 import net.mamoe.mirai.console.data.AutoSavePluginConfig
 import net.mamoe.mirai.console.data.ReadOnlyPluginConfig
 import net.mamoe.mirai.console.data.value
+import net.mamoe.mirai.message.data.MessageChain
+import net.mamoe.mirai.message.data.messageChainOf
 import org.example.myplugin.DataTest1.provideDelegate
+import java.util.*
 import java.util.concurrent.ConcurrentHashMap
 
 
@@ -23,6 +26,9 @@ object UsingDerivedMap : AutoSavePluginConfig("data") {
     var p4 by value<HashMap<String, String>>()
     var p3 by value<ConcurrentHashMap<String, String>>()
 
+    var p8 by value<List<String>>()
     var p5 by value<ArrayList<String>>()
-    var p6 by value<AbstractList<String>>()
+    var p7 by value<AbstractList<String>>()
+
+    var p6 by value<MessageChain>(messageChainOf()) // no error
 }
diff --git a/mirai-console/tools/intellij-plugin/src/diagnostics/PluginDataValuesChecker.kt b/mirai-console/tools/intellij-plugin/src/diagnostics/PluginDataValuesChecker.kt
index c12edee57..2997a6ac0 100644
--- a/mirai-console/tools/intellij-plugin/src/diagnostics/PluginDataValuesChecker.kt
+++ b/mirai-console/tools/intellij-plugin/src/diagnostics/PluginDataValuesChecker.kt
@@ -1,10 +1,10 @@
 /*
- * Copyright 2019-2021 Mamoe Technologies and contributors.
+ * Copyright 2019-2022 Mamoe Technologies and contributors.
  *
- *  此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
- *  Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
+ * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
+ * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
  *
- *  https://github.com/mamoe/mirai/blob/master/LICENSE
+ * https://github.com/mamoe/mirai/blob/dev/LICENSE
  */
 
 @file:Suppress("MemberVisibilityCanBePrivate")
@@ -31,6 +31,7 @@ import org.jetbrains.kotlin.resolve.calls.checkers.CallCheckerContext
 import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
 import org.jetbrains.kotlin.resolve.checkers.DeclarationChecker
 import org.jetbrains.kotlin.resolve.checkers.DeclarationCheckerContext
+import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
 import org.jetbrains.kotlin.resolve.descriptorUtil.isSubclassOf
 import org.jetbrains.kotlin.types.KotlinType
 import org.jetbrains.kotlin.types.SimpleType
@@ -114,6 +115,9 @@ class PluginDataValuesChecker : CallChecker, DeclarationChecker {
         val factory = when {
             jetTypeFqn == "java.util.concurrent.ConcurrentHashMap" -> MiraiConsoleErrors.USING_DERIVED_CONCURRENT_MAP_TYPE
 
+            classDescriptor.fqNameSafe.asString()
+                .startsWith("net.mamoe.mirai.message.data.") -> null // Don't report for MessageChain
+
             classDescriptor.isSubclassOf(builtIns.list) && jetTypeFqn != "kotlin.collections.List" -> {
                 if (classDescriptor.isSubclassOf(builtIns.mutableList)) {
                     if (jetTypeFqn != "kotlin.collections.MutableList" && jetTypeFqn != "java.util.List") {